diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7a7bb31ea54..ad802d791e8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -114,18 +114,33 @@ apps/desktop/destkop_native/core/src/biometric/ @bitwarden/team-key-management-d apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-key-management-dev apps/browser/src/background/nativeMessaging.background.ts @bitwarden/team-key-management-dev -## DevOps team files ## -/.github/workflows @bitwarden/dept-devops - -# DevOps for Docker changes. -**/Dockerfile @bitwarden/dept-devops -**/*.Dockerfile @bitwarden/dept-devops -**/.dockerignore @bitwarden/dept-devops -**/entrypoint.sh @bitwarden/dept-devops - ## Locales ## apps/browser/src/_locales/en/messages.json apps/browser/store/locales/en apps/cli/src/locales/en/messages.json apps/desktop/src/locales/en/messages.json apps/web/src/locales/en/messages.json + +## BRE team owns these workflows ## +.github/workflows/brew-bump-desktop.yml @bitwarden/dept-bre +.github/workflows/deploy-web.yml @bitwarden/dept-bre +.github/workflows/publish-cli.yml @bitwarden/dept-bre +.github/workflows/publish-desktop.yml @bitwarden/dept-bre +.github/workflows/publish-web.yml @bitwarden/dept-bre +.github/workflows/retrieve-current-desktop-rollout.yml @bitwarden/dept-bre +.github/workflows/staged-rollout-desktop.yml @bitwarden/dept-bre + +## Shared ownership workflows ## +.github/workflows/release-browser.yml +.github/workflows/release-cli.yml +.github/workflows/release-desktop-beta.yml +.github/workflows/release-desktop.yml +.github/workflows/release-web.yml +.github/workflows/version-auto-bump.yml +.github/workflows/version-bump.yml + +## Docker files have shared ownership ## +**/Dockerfile +**/*.Dockerfile +**/.dockerignore +**/entrypoint.sh diff --git a/.github/renovate.json b/.github/renovate.json index a1200912dc8..562622807c2 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -81,7 +81,6 @@ "cross-env", "del", "gulp", - "gulp-filter", "gulp-if", "gulp-json-editor", "gulp-replace", diff --git a/.github/workflows/auto-branch-updater.yml b/.github/workflows/auto-branch-updater.yml index 372aa92eba0..97f020fde7b 100644 --- a/.github/workflows/auto-branch-updater.yml +++ b/.github/workflows/auto-branch-updater.yml @@ -1,4 +1,3 @@ ---- name: Auto Update Branch on: @@ -29,7 +28,7 @@ jobs: run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: 'eu-web-${{ steps.setup.outputs.branch }}' fetch-depth: 0 diff --git a/.github/workflows/automatic-issue-responses.yml b/.github/workflows/automatic-issue-responses.yml index 289b8bd662b..e38f8103cb5 100644 --- a/.github/workflows/automatic-issue-responses.yml +++ b/.github/workflows/automatic-issue-responses.yml @@ -1,4 +1,3 @@ ---- name: Automatic issue responses on: issues: diff --git a/.github/workflows/automatic-pull-request-responses.yml b/.github/workflows/automatic-pull-request-responses.yml index 16e1a46c052..6bd069d21ac 100644 --- a/.github/workflows/automatic-pull-request-responses.yml +++ b/.github/workflows/automatic-pull-request-responses.yml @@ -1,4 +1,3 @@ ---- name: Automatic pull request responses on: pull_request: diff --git a/.github/workflows/brew-bump-desktop.yml b/.github/workflows/brew-bump-desktop.yml index 31239aa9b29..1b3c99128bf 100644 --- a/.github/workflows/brew-bump-desktop.yml +++ b/.github/workflows/brew-bump-desktop.yml @@ -1,4 +1,3 @@ ---- name: Bump Desktop Cask on: diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 096fff8db34..20a36dc5b23 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -1,4 +1,3 @@ ---- name: Build Browser on: @@ -43,7 +42,7 @@ jobs: node_version: ${{ steps.retrieve-node-version.outputs.node_version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Get Package Version id: gen_vars @@ -73,7 +72,7 @@ jobs: working-directory: apps/browser steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Testing locales - extName length run: | @@ -111,7 +110,7 @@ jobs: _NODE_VERSION: ${{ needs.setup.outputs.node_version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -169,63 +168,63 @@ jobs: working-directory: browser-source/apps/browser - name: Upload Opera artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: dist-opera-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-opera.zip if-no-files-found: error - name: Upload Opera MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: DO-NOT-USE-FOR-PROD-dist-opera-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-opera-mv3.zip if-no-files-found: error - name: Upload Chrome MV3 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-chrome-mv3.zip if-no-files-found: error - name: Upload Chrome MV3 Beta artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: DO-NOT-USE-FOR-PROD-dist-chrome-MV3-beta-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-chrome-mv3-beta.zip if-no-files-found: error - name: Upload Firefox artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: dist-firefox-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-firefox.zip if-no-files-found: error - name: Upload Firefox MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: DO-NOT-USE-FOR-PROD-dist-firefox-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-firefox-mv3.zip if-no-files-found: error - name: Upload Edge artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: dist-edge-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-edge.zip if-no-files-found: error - name: Upload Edge MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: DO-NOT-USE-FOR-PROD-dist-edge-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-edge-mv3.zip if-no-files-found: error - name: Upload browser source - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: browser-source-${{ env._BUILD_NUMBER }}.zip path: browser-source.zip @@ -242,7 +241,7 @@ jobs: _NODE_VERSION: ${{ needs.setup.outputs.node_version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -340,7 +339,7 @@ jobs: ls -la - name: Upload Safari artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: dist-safari-${{ env._BUILD_NUMBER }}.zip path: apps/browser/dist/dist-safari.zip @@ -355,7 +354,7 @@ jobs: - build-safari steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index b5b212423ab..f88c4767407 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -1,4 +1,3 @@ ---- name: Build CLI on: @@ -43,7 +42,7 @@ jobs: node_version: ${{ steps.retrieve-node-version.outputs.node_version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Get Package Version id: retrieve-package-version @@ -84,7 +83,7 @@ jobs: _WIN_PKG_VERSION: 3.5 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup Unix Vars run: | @@ -130,14 +129,14 @@ jobs: matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt - name: Upload unix zip asset - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload unix checksum asset - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -162,7 +161,7 @@ jobs: _WIN_PKG_VERSION: 3.5 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup Windows builder run: | @@ -269,14 +268,14 @@ jobs: -t sha256 | Out-File -Encoding ASCII ./dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${env:_PACKAGE_VERSION}.txt - name: Upload windows zip asset - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload windows checksum asset - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -284,18 +283,18 @@ jobs: - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - + - name: Zip NPM Build Artifact run: Get-ChildItem -Path .\build | Compress-Archive -DestinationPath .\bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip path: apps/cli/bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip @@ -312,7 +311,7 @@ jobs: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Print environment run: | @@ -364,14 +363,14 @@ jobs: run: sudo snap remove bw - name: Upload snap asset - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload snap checksum asset - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 5022184bd05..2c89e0d156f 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1,4 +1,3 @@ ---- name: Build Desktop on: @@ -38,7 +37,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Verify run: | @@ -67,7 +66,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Get Package Version id: retrieve-version @@ -140,7 +139,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -169,7 +168,7 @@ jobs: working-directory: ./ - name: Cache Native Module - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 id: cache with: path: | @@ -194,42 +193,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -250,7 +249,7 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -299,7 +298,7 @@ jobs: working-directory: ./ - name: Cache Native Module - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 id: cache with: path: | @@ -354,91 +353,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -458,7 +457,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -484,14 +483,14 @@ jobs: - name: Cache Build id: build-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Cache Safari id: safari-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -584,7 +583,7 @@ jobs: working-directory: ./ - name: Cache Native Module - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 id: cache with: path: | @@ -624,7 +623,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -650,14 +649,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -750,7 +749,7 @@ jobs: working-directory: ./ - name: Cache Native Module - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 id: cache with: path: | @@ -799,28 +798,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -843,7 +842,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -869,14 +868,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -976,7 +975,7 @@ jobs: working-directory: ./ - name: Cache Native Module - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 id: cache with: path: | @@ -1025,7 +1024,7 @@ jobs: run: npm run pack:mac:mas - name: Upload .pkg artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -1090,7 +1089,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -1111,14 +1110,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -1211,7 +1210,7 @@ jobs: working-directory: ./ - name: Cache Native Module - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 id: cache with: path: | @@ -1263,7 +1262,7 @@ jobs: zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - name: Upload masdev artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip @@ -1281,7 +1280,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 4551a7baac8..ec09f25ac19 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -1,4 +1,3 @@ ---- name: Build Web on: @@ -45,7 +44,7 @@ jobs: node_version: ${{ steps.retrieve-node-version.outputs.node_version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Get GitHub sha as version id: version @@ -91,7 +90,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Node uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -130,7 +129,7 @@ jobs: run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build - name: Upload ${{ matrix.name }} artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: web-${{ env._VERSION }}-${{ matrix.name }}.zip path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip @@ -157,7 +156,7 @@ jobs: _VERSION: ${{ needs.setup.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Check Branch to Publish env: @@ -234,7 +233,7 @@ jobs: run: echo "name=$_AZ_REGISTRY/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT - name: Build Docker image - uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0 + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 with: context: apps/web file: apps/web/Dockerfile @@ -255,7 +254,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index b52ecc1c40a..d6f63d48032 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -1,4 +1,3 @@ ---- name: Chromatic on: @@ -25,7 +24,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 @@ -44,7 +43,7 @@ jobs: - name: Cache NPM id: npm-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: "~/.npm" key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }} @@ -57,7 +56,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@f4e60a7072abcac4203f4ca50613f28e199a52ba # v11.10.4 + uses: chromaui/action@bbbf288765438d5fd2be13e1d80d542a39e74108 # v11.12.1 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index b319e5365f7..dfcd3294b01 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -1,4 +1,3 @@ ---- name: Crowdin Sync on: @@ -23,7 +22,7 @@ jobs: crowdin_project_id: "308189" steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index d0f791aa000..5cc4eb90861 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -1,4 +1,3 @@ ---- name: Deploy Web Vault run-name: Deploy Web Vault to ${{ inputs.environment }} from ${{ inputs.branch-or-tag }} diff --git a/.github/workflows/enforce-labels.yml b/.github/workflows/enforce-labels.yml index a98c4ae1bea..40ddfe7739f 100644 --- a/.github/workflows/enforce-labels.yml +++ b/.github/workflows/enforce-labels.yml @@ -1,4 +1,3 @@ ---- name: Enforce PR labels on: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ad8c5dd40dc..db7fef83fb8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,3 @@ ---- name: Lint on: @@ -21,7 +20,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Lint filenames (no capital characters) run: | diff --git a/.github/workflows/locales-lint.yml b/.github/workflows/locales-lint.yml index db2c66f7b88..ef944526111 100644 --- a/.github/workflows/locales-lint.yml +++ b/.github/workflows/locales-lint.yml @@ -1,4 +1,3 @@ ---- name: Locales lint for Crowdin on: @@ -15,9 +14,9 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Checkout base branch repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ github.event.pull_request.base.sha }} path: base diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index f9c4b85e8ab..c9a4e841ea8 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -1,4 +1,3 @@ ---- name: Publish CLI run-name: Publish CLI ${{ inputs.publish_type }} @@ -92,7 +91,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -129,7 +128,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -169,7 +168,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 0816a86b1b5..c46a7a27601 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -1,4 +1,3 @@ ---- name: Publish Desktop run-name: Publish Desktop ${{ inputs.publish_type }} @@ -125,7 +124,7 @@ jobs: secrets: "aws-electron-access-id, aws-electron-access-key, aws-electron-bucket-name" - + - name: Create artifacts directory run: mkdir -p apps/desktop/artifacts @@ -184,7 +183,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} steps: - name: Checkout Repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -228,7 +227,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} steps: - name: Checkout Repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Print Environment run: | diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index 02a60ee4222..7e0e8737344 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -1,4 +1,3 @@ ---- name: Publish Web run-name: Publish Web ${{ inputs.publish_type }} @@ -27,7 +26,7 @@ jobs: tag_version: ${{ steps.version.outputs.tag }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Branch check if: ${{ inputs.publish_type != 'Dry Run' }} @@ -67,7 +66,7 @@ jobs: echo "Github Release Option: $_RELEASE_OPTION" - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 ########## ACR ########## - name: Login to Azure - PROD Subscription diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index f190889414b..aed9ab293e8 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -1,4 +1,3 @@ ---- name: Release Browser run-name: Release Browser ${{ inputs.release_type }} @@ -27,7 +26,7 @@ jobs: release-version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -56,7 +55,7 @@ jobs: needs: setup steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Testing locales - extName length run: | diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 8f5123b50b2..8660744f944 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -1,4 +1,3 @@ ---- name: Release CLI run-name: Release CLI ${{ inputs.release_type }} @@ -27,7 +26,7 @@ jobs: release-version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 23b06d71dd3..7518daf0b16 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -1,4 +1,3 @@ ---- name: Release Desktop Beta on: @@ -24,7 +23,7 @@ jobs: node_version: ${{ steps.retrieve-node-version.outputs.node_version }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Branch check run: | @@ -125,7 +124,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ needs.setup.outputs.branch-name }} @@ -159,42 +158,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: ${{ needs.setup.outputs.release-channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-linux.yml @@ -215,7 +214,7 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ needs.setup.outputs.branch-name }} @@ -300,91 +299,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: ${{ needs.setup.outputs.release-channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release-channel }}.yml @@ -404,7 +403,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ needs.setup.outputs.branch-name }} @@ -427,14 +426,14 @@ jobs: - name: Cache Build id: build-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Cache Safari id: safari-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -538,7 +537,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ needs.setup.outputs.branch-name }} @@ -561,14 +560,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -708,28 +707,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: ${{ needs.setup.outputs.release-channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-mac.yml @@ -751,7 +750,7 @@ jobs: working-directory: apps/desktop steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ needs.setup.outputs.branch-name }} @@ -774,14 +773,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -916,7 +915,7 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - name: Upload .pkg artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -1011,7 +1010,7 @@ jobs: - release steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup git config run: | diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index d69c15559b3..b0ddc4b804d 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -1,4 +1,3 @@ ---- name: Release Desktop run-name: Release Desktop ${{ inputs.release_type }} @@ -27,7 +26,7 @@ jobs: release-channel: ${{ steps.release-channel.outputs.channel }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 92f5701889b..e3462a98fb6 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -1,4 +1,3 @@ ---- name: Release Web run-name: Release Web ${{ inputs.release_type }} @@ -24,7 +23,7 @@ jobs: tag_version: ${{ steps.version.outputs.tag }} steps: - name: Checkout repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/retrieve-current-desktop-rollout.yml b/.github/workflows/retrieve-current-desktop-rollout.yml index 45a2bf5ce42..2ab3072f566 100644 --- a/.github/workflows/retrieve-current-desktop-rollout.yml +++ b/.github/workflows/retrieve-current-desktop-rollout.yml @@ -1,4 +1,3 @@ ---- name: Retrieve Current Desktop Rollout on: diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index f15a14dd117..143d049bd63 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -1,4 +1,3 @@ ---- name: Scan on: @@ -27,12 +26,12 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@9fda5a4a2c297608117a5a56af424502a9192e57 # 2.0.34 + uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: @@ -47,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9 + uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: sarif_file: cx_result.sarif @@ -61,13 +60,13 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@eb211723266fe8e83102bac7361f0a05c3ac1d1b # v3.0.0 + uses: sonarsource/sonarcloud-github-action@383f7e52eae3ab0510c3cb0e7d9d150bbaeab838 # v3.1.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/staged-rollout-desktop.yml b/.github/workflows/staged-rollout-desktop.yml index 1f751507640..91250a443f2 100644 --- a/.github/workflows/staged-rollout-desktop.yml +++ b/.github/workflows/staged-rollout-desktop.yml @@ -1,4 +1,3 @@ ---- name: Staged Rollout Desktop on: diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 4d3085ce003..6caa7b99331 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -1,4 +1,3 @@ ---- name: 'Close stale issues and PRs' on: workflow_dispatch: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fd378213c9f..4ea08a24373 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,3 @@ ---- name: Testing on: @@ -41,7 +40,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Get Node Version id: retrieve-node-version @@ -86,13 +85,13 @@ jobs: fail-on-error: true - name: Upload coverage to codecov.io - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 if: ${{ needs.check-test-secrets.outputs.available == 'true' }} env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Upload results to codecov.io - uses: codecov/test-results-action@1b5b448b98e58ba90d1a1a1d9fcb72ca2263be46 # v1.0.0 + uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1 if: ${{ needs.check-test-secrets.outputs.available == 'true' }} env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -121,7 +120,7 @@ jobs: sudo apt-get install -y gnome-keyring dbus-x11 - name: Check out repo - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Build working-directory: ./apps/desktop/desktop_native diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index f10abee300d..cc6feeba026 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -1,4 +1,3 @@ ---- name: Auto Bump Desktop Version on: diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index d18097ee1c2..7f6dfef79cf 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,4 +1,3 @@ ---- name: Version Bump on: @@ -58,7 +57,7 @@ jobs: fi - name: Checkout Branch - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: main @@ -533,7 +532,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout Branch - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: main diff --git a/apps/browser/gulpfile.js b/apps/browser/gulpfile.js index 5212fc58659..573f86efc18 100644 --- a/apps/browser/gulpfile.js +++ b/apps/browser/gulpfile.js @@ -14,23 +14,9 @@ const betaBuild = process.env.BETA_BUILD === "1"; const paths = { build: "./build/", dist: "./dist/", - node_modules: "./node_modules/", - popupDir: "./src/popup/", - cssDir: "./src/popup/css/", safari: "./src/safari/", }; -const filters = { - fonts: [ - "!build/popup/fonts/*", - "build/popup/fonts/Open_Sans*.woff", - "build/popup/fonts/bwi-font.woff2", - "build/popup/fonts/bwi-font.woff", - "build/popup/fonts/bwi-font.ttf", - ], - safari: ["!build/safari/**/*"], -}; - /** * Converts a number to a tuple containing two Uint16's * @param num {number} This number is expected to be a integer style number with no decimals @@ -64,11 +50,9 @@ function distFileName(browserName, ext) { async function dist(browserName, manifest) { const { default: zip } = await import("gulp-zip"); - const { default: filter } = await import("gulp-filter"); return gulp .src(paths.build + "**/*") - .pipe(filter(["**"].concat(filters.fonts).concat(filters.safari))) .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_" + browserName))) .pipe(gulpif("manifest.json", jeditor(manifest))) .pipe(zip(distFileName(browserName, "zip"))) @@ -192,8 +176,6 @@ function distSafariApp(cb, subBuildPath) { return new Promise((resolve) => proc.on("close", resolve)); }) .then(async () => { - const { default: filter } = await import("gulp-filter"); - const libs = fs .readdirSync(builtAppexFrameworkPath) .filter((p) => p.endsWith(".dylib")) @@ -237,13 +219,10 @@ function safariCopyAssets(source, dest) { } async function safariCopyBuild(source, dest) { - const { default: filter } = await import("gulp-filter"); - return new Promise((resolve, reject) => { gulp .src(source) .on("error", reject) - .pipe(filter(["**"].concat(filters.fonts))) .pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_safari"))) .pipe( gulpif( diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 592ca6238ea..ff9fa87252e 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "التحقق مطلوب", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index f9f2cb10b56..ca4f3e5a0ef 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -11,7 +11,7 @@ "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { - "message": "Güvənli anbarınıza müraciət etmək üçün giriş edin və ya yeni bir hesab yaradın." + "message": "Güvənli seyfinizə müraciət etmək üçün giriş edin və ya yeni bir hesab yaradın." }, "inviteAccepted": { "message": "Dəvət qəbul edildi" @@ -44,7 +44,7 @@ "message": "Ana parol" }, "masterPassDesc": { - "message": "Ana parol, anbarınıza müraciət etmək üçün istifadə edəcəyiniz paroldur. Ana parolu yadda saxlamaq çox vacibdir. Unutsanız, parolu geri qaytarmağın heç bir yolu yoxdur." + "message": "Ana parol, seyfinizə müraciət etmək üçün istifadə edəcəyiniz paroldur. Ana parolu yadda saxlamaq çox vacibdir. Unutsanız, parolu bərpa etməyin heç bir yolu yoxdur." }, "masterPassHintDesc": { "message": "Ana parol məsləhəti, unutduğunuz parolunuzu xatırlamağınıza kömək edir." @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Təşkilata qoşul" }, + "joinOrganizationName": { + "message": "$ORGANIZATIONNAME$ - qoşul", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Bu ana parol təyin edərək bu təşkilata qoşulmağı tamamlayın." }, @@ -78,13 +87,13 @@ "message": "Vərəq" }, "vault": { - "message": "Anbar" + "message": "Seyf" }, "myVault": { - "message": "Anbarım" + "message": "Seyfim" }, "allVaults": { - "message": "Bütün anbarlar" + "message": "Bütün seyflər" }, "tools": { "message": "Alətlər" @@ -180,10 +189,10 @@ "message": "Kimlik əlavə et" }, "unlockVaultMenu": { - "message": "Anbarınızın kilidini açın" + "message": "Seyfinizin kilidini açın" }, "loginToVaultMenu": { - "message": "Anbarınıza giriş edin" + "message": "Seyfinizə giriş edin" }, "autoFillInfo": { "message": "Hazırkı brauzer vərəqi üçün avto-doldurulacaq giriş məlumatları yoxdur." @@ -341,7 +350,7 @@ "message": "Heç bir qovluq əlavə edilmədi" }, "createFoldersToOrganize": { - "message": "Anbar elementlərinizi təşkil etmək üçün qovluq yaradın" + "message": "Seyf elementlərinizi təşkil etmək üçün qovluq yaradın" }, "deleteFolderPermanently": { "message": "Bu qovluğu həmişəlik silmək istədiyinizə əminsiniz?" @@ -371,7 +380,7 @@ "message": "Sinxr" }, "syncVaultNow": { - "message": "Anbarı indi sinxronlaşdır" + "message": "Seyfi indi sinxronlaşdır" }, "lastSync": { "message": "Son sinxr:" @@ -494,7 +503,7 @@ "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { - "message": "Anbarda axtar" + "message": "Seyfdə axtar" }, "edit": { "message": "Düzəliş et" @@ -575,7 +584,7 @@ "message": "Kilid açma seçimləri" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Anbar vaxt bitməsi əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." + "message": "Seyf vaxt bitmə əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." }, "unlockMethodNeeded": { "message": "Ayarlarda bir kilid açma üsulu qurun" @@ -584,7 +593,7 @@ "message": "Seans vaxt bitməsi" }, "vaultTimeoutHeader": { - "message": "Anbar vaxtının bitməsi" + "message": "Seyf vaxtının bitməsi" }, "otherOptions": { "message": "Digər seçimlər" @@ -602,10 +611,10 @@ "message": "Kimliyi doğrula" }, "yourVaultIsLocked": { - "message": "Anbarınız kilidlənib. Davam etmək üçün kimliyinizi doğrulayın." + "message": "Seyfiniz kilidlənib. Davam etmək üçün kimliyinizi doğrulayın." }, "yourVaultIsLockedV2": { - "message": "Anbarınız kilidlənib" + "message": "Seyfiniz kilidlənib" }, "yourAccountIsLocked": { "message": "Hesabınız kilidlənib" @@ -633,7 +642,7 @@ "message": "Yararsız ana parol" }, "vaultTimeout": { - "message": "Anbara müraciət bitəcək" + "message": "Seyf vaxtının bitməsi" }, "vaultTimeout1": { "message": "Vaxt bitmə" @@ -836,7 +845,7 @@ "message": "Qovluq əlavə edildi" }, "twoStepLoginConfirmation": { - "message": "İki addımlı giriş, güvənlik açarı, kimlik doğrulayıcı tətbiq, SMS, telefon zəngi və ya e-poçt kimi digər cihazlarla girişinizi doğrulamanızı tələb edərək hesabınızı daha da güvənli edir. İki addımlı giriş, bitwarden.com veb anbarında qurula bilər. Veb saytı indi ziyarət etmək istəyirsiniz?" + "message": "İki addımlı giriş, güvənlik açarı, kimlik doğrulayıcı tətbiq, SMS, telefon zəngi və ya e-poçt kimi digər cihazlarla girişinizi doğrulamanızı tələb edərək hesabınızı daha da güvənli edir. İki addımlı giriş, bitwarden.com veb seyfində qurula bilər. Veb saytı indi ziyarət etmək istəyirsiniz?" }, "twoStepLoginConfirmationContent": { "message": "Bitwarden veb tətbiqində iki addımlı girişi quraraq hesabınızı daha güvənli edin." @@ -929,16 +938,16 @@ "message": "Giriş əlavə etmək üçün soruş" }, "vaultSaveOptionsTitle": { - "message": "Anbar seçimlərində saxla" + "message": "Seyf seçimlərində saxla" }, "addLoginNotificationDesc": { - "message": "Anbarınızda tapılmayan bir elementin əlavə edilməsi soruşulsun." + "message": "Seyfinizdə tapılmayan elementin əlavə edilməsi soruşulsun." }, "addLoginNotificationDescAlt": { - "message": "Anbarınızda tapılmayan bir elementin əlavə edilməsi soruşulsun. Giriş etmiş bütün hesablara aiddir." + "message": "Seyfinizdə tapılmayan elementin əlavə edilməsi soruşulsun. Giriş etmiş bütün hesablara aiddir." }, "showCardsInVaultView": { - "message": "Kartları, Anbar görünüşündə Avto-doldurma təklifləri olaraq göstər" + "message": "Kartları, Seyf görünüşündə Avto-doldurma təklifləri olaraq göstər" }, "showCardsCurrentTab": { "message": "Kartları Vərəq səhifəsində göstər" @@ -947,7 +956,7 @@ "message": "Asan avto-doldurma üçün Vərəq səhifəsində kart elementlərini sadalayın." }, "showIdentitiesInVaultView": { - "message": "Kimlikləri, Anbar görünüşündə Avto-doldurma təklifləri olaraq göstər" + "message": "Kimlikləri, Seyf görünüşündə Avto-doldurma təklifləri olaraq göstər" }, "showIdentitiesCurrentTab": { "message": "Vərəq səhifəsində kimlikləri göstər" @@ -982,7 +991,7 @@ "message": "Keçid açarlarını saxlamağı və istifadə etməyi soruş" }, "usePasskeysDesc": { - "message": "Yeni keçid açarlarını saxlamağı və ya anbarınızda saxlanılan keçid açarları ilə giriş etmək soruşulsun. Giriş etmiş bütün hesablara aiddir." + "message": "Yeni keçid açarlarını saxlamağı və ya seyfinizdə saxlanılan keçid açarları ilə giriş etmək soruşulsun. Giriş etmiş bütün hesablara aiddir." }, "notificationChangeDesc": { "message": "Bu parolu \"Bitwarden\"də güncəlləmək istəyirsiniz?" @@ -991,7 +1000,7 @@ "message": "Güncəllə" }, "notificationUnlockDesc": { - "message": "Avto-doldurma tələblərini tamamlamaq üçün Bitwarden anbarınızın kilidini açın." + "message": "Avto-doldurma tələblərini tamamlamaq üçün Bitwarden seyfinizin kilidini açın." }, "notificationUnlock": { "message": "Kilidi aç" @@ -1013,7 +1022,7 @@ "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { - "message": "Avto-doldurma kimi əməliyyatları icra edərkən giriş etmə prosesi üçün URI uyuşma aşkarlamasının idarə edliəcəyi ilkin yolu seçin." + "message": "Avto-doldurma kimi əməliyyatları icra edərkən giriş etmə prosesi üçün URI uyuşma aşkarlamasının idarə ediləcəyi ilkin yolu seçin." }, "theme": { "message": "Tema" @@ -1040,7 +1049,7 @@ "message": "Buradan xaricə köçür" }, "exportVault": { - "message": "Anbarı xaricə köçür" + "message": "Seyfi xaricə köçür" }, "fileFormat": { "message": "Fayl formatı" @@ -1074,7 +1083,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Anbarın xaricə köçürülməsini təsdiqlə" + "message": "Seyfi xaricə köçürməyi təsdiqlə" }, "exportWarningDesc": { "message": "Xaricə köçürdüyünüz bu fayldakı datanız şifrələnməmiş formatdadır. Bu faylı güvənli olmayan kanallar (e-poçt kimi) üzərində saxlamamalı və ya göndərməməlisiniz. İşiniz bitdikdən sonra faylı dərhal silin." @@ -1086,13 +1095,13 @@ "message": "Hesab şifrələmə açarları, hər Bitwarden istifadəçi hesabı üçün unikaldır, buna görə də şifrələnmiş bir xaricə köçürməni, fərqli bir hesaba köçürə bilməzsiniz." }, "exportMasterPassword": { - "message": "Anbar datanızı xaricə köçürmək üçün ana parolunuzu daxil edin." + "message": "Seyf datanızı xaricə köçürmək üçün ana parolunuzu daxil edin." }, "shared": { "message": "Paylaşılan" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business, bir təşkilat hesabı istifadə edərək anbar elementlərinizi başqaları ilə paylaşmağınıza imkan verir. Daha ətraflı bitwarden.com veb saytında öyrənə bilərsiniz." + "message": "Bitwarden for Business, bir təşkilatı istifadə edərək seyf elementlərinizi başqaları ilə paylaşmağınıza imkan verir. Daha ətraflı məlumat üçün bitwarden.com saytını ziyarət edin." }, "moveToOrganization": { "message": "Təşkilata daşı" @@ -1165,7 +1174,7 @@ "message": "Özəllik əlçatmazdır" }, "encryptionKeyMigrationRequired": { - "message": "Şifrələmə açarının daşınması tələb olunur. Şifrələmə açarınızı güncəlləmək üçün lütfən veb anbar üzərindən giriş edin." + "message": "Şifrələmə açarının daşınması tələb olunur. Şifrələmə açarınızı güncəlləmək üçün lütfən veb seyfinizə giriş edin." }, "premiumMembership": { "message": "Premium üzvlük" @@ -1174,7 +1183,7 @@ "message": "Üzvlüyü idarə edin" }, "premiumManageAlert": { - "message": "Üzvlüyünüzü bitwarden.com veb anbarında idarə edə bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" + "message": "Üzvlüyünüzü bitwarden.com veb seyfində idarə edə bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" }, "premiumRefresh": { "message": "Üzvlüyü təzələ" @@ -1186,7 +1195,7 @@ "message": "Premium üzvlük üçün qeydiyyatdan keçin və bunları əldə edin:" }, "ppremiumSignUpStorage": { - "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi" + "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş anbar sahəsi" }, "premiumSignUpEmergency": { "message": "Fövqəladə hal müraciəti" @@ -1195,10 +1204,10 @@ "message": "YubiKey və Duo kimi mülkiyyətçi iki addımlı giriş seçimləri." }, "ppremiumSignUpReports": { - "message": "Anbarınızın güvənliyini təmin etmək üçün parol gigiyenası, hesab sağlamlığı və data pozuntusu hesabatları." + "message": "Seyfinizi güvəndə saxlamaq üçün parol gigiyenası, hesab sağlamlığı və data pozuntusu hesabatları." }, "ppremiumSignUpTotp": { - "message": "Anbarınızdakı hesablar üçün TOTP doğrulama kodu (2FA) yaradıcısı." + "message": "Seyfinizdəki girişlər üçün TOTP doğrulama kodu (2FA) yaradıcısı." }, "ppremiumSignUpSupport": { "message": "Prioritet müştəri dəstəyi." @@ -1210,7 +1219,7 @@ "message": "Premium satın al" }, "premiumPurchaseAlert": { - "message": "Premium üzvlüyü bitwarden.com veb anbarında satın ala bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" + "message": "Premium üzvlüyü bitwarden.com veb seyfində satın ala bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" }, "premiumPurchaseAlertV2": { "message": "Bitwarden veb tətbiqindəki hesab ayarlarınızda Premium satın ala bilərsiniz." @@ -1384,7 +1393,7 @@ "message": "API server URL-si" }, "webVaultUrl": { - "message": "Veb anbar server URL-si" + "message": "Veb seyf server URL-si" }, "identityUrl": { "message": "Kimlik server URL-si" @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Avto-doldurma təkliflərini form xanalarında göstər" }, + "showInlineMenuIdentitiesLabel": { + "message": "Kimlikləri təklif kimi göstər" + }, + "showInlineMenuCardsLabel": { + "message": "Kartları təklif kimi göstər" + }, "showInlineMenuOnIconSelectionLabel": { "message": "İkon seçildikdə təklifləri göstər" }, @@ -1482,10 +1497,10 @@ "message": "Səhifə yüklənəndə avto-doldurma" }, "commandOpenPopup": { - "message": "Anbarı açılan pəncərədə aç" + "message": "Seyfi pəncərədə aç" }, "commandOpenSidebar": { - "message": "Anbarı yan çubuqda aç" + "message": "Seyfi yan çubuqda aç" }, "commandAutofillLoginDesc": { "message": "Hazırkı veb sayt üçün son istifadə edilən girişi avto-doldur" @@ -1500,7 +1515,7 @@ "message": "Təsadüfi yeni bir parol yarat və lövhəyə kopyala" }, "commandLockVaultDesc": { - "message": "Anbarı kilidlə" + "message": "Seyfi kilidlə" }, "customFields": { "message": "Özəl xanalar" @@ -1882,7 +1897,7 @@ "description": "ex. Date this password was updated" }, "neverLockWarning": { - "message": "\"Heç vaxt\" seçimini istifadə etmək istədiyinizə əminsiniz? Kilid seçimini \"Heç vaxt\" olaraq ayarlasanız, anbarınızın şifrələmə açarı cihazınızda saxlanılacaq. Bu seçimi istifadə etsəniz, cihazınızı daha yaxşı mühafizə etməlisiniz." + "message": "\"Heç vaxt\"i seçmək istədiyinizə əminsiniz? Kilid seçimini \"Heç vaxt\" olaraq ayarlasanız, seyfinizin şifrələmə açarı cihazınızda saxlanılacaq. Bu seçimi istifadə etsəniz, cihazınızı daha yaxşı mühafizə etdiyinizə əmin olmalısınız." }, "noOrganizationsList": { "message": "Heç bir təşkilata aid deyilsiniz. Təşkilatlar, elementlərinizi digər istifadəçilərlə güvənli şəkildə paylaşmağınızı təmin edir." @@ -1996,7 +2011,7 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultTimeoutAction": { - "message": "Anbar vaxtının bitmə əməliyyatı" + "message": "Seyf vaxtının bitmə əməliyyatı" }, "vaultTimeoutAction1": { "message": "Vaxt bitmə əməliyyatı" @@ -2031,7 +2046,7 @@ "message": "Artıq hesabınız var?" }, "vaultTimeoutLogOutConfirmation": { - "message": "Çıxış etdikdə anbarınıza bütün müraciətiniz dayanacaq və vaxt bitməsindən sonra onlayn kimlik doğrulaması tələb olunacaq. Bu ayarı istifadə etmək istədiyinizə əminsiniz?" + "message": "Çıxış etdikdə, seyfinizə bütün müraciətiniz dayanacaq və vaxt bitməsindən sonra onlayn kimlik doğrulaması tələb olunacaq. Bu ayarı istifadə etmək istədiyinizə əminsiniz?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Vaxt bitmə əməliyyat təsdiqi" @@ -2199,7 +2214,7 @@ "message": "Biometrik açarı uyuşmazlığı" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometrik kilidini açma uğursuz oldu. Biometrik sirr açarı anbarın kilidini aça bilmədi. Lütfən biometriki yenidən qurmağa çalışın." + "message": "Biometrik ilə kilid açma uğursuz oldu. Biometrik sirr açarı seyfin kilidini aça bilmədi. Lütfən biometriki yenidən qurmağa çalışın." }, "biometricsNotEnabledTitle": { "message": "Biometriklər qurulmayıb" @@ -2244,13 +2259,13 @@ "message": "Bu əməliyyat yan çubuqda icra edilə bilməz. Lütfən açılan pəncərədə yenidən sınayın." }, "personalOwnershipSubmitError": { - "message": "Müəssisə Siyasətinə görə, elementləri şəxsi anbarınızda saxlamağınız məhdudlaşdırılıb. Sahiblik seçimini təşkilat olaraq dəyişdirin və mövcud kolleksiyalar arasından seçim edin." + "message": "Müəssisə Siyasətinə görə, elementləri şəxsi seyfinizdə saxlamağınız məhdudlaşdırılıb. Sahiblik seçimini təşkilat olaraq dəyişdirin və mövcud kolleksiyalar arasından seçim edin." }, "personalOwnershipPolicyInEffect": { "message": "Bir təşkilat siyasəti, sahiblik seçimlərinizə təsir edir." }, "personalOwnershipPolicyInEffectImports": { - "message": "Bir təşkilat siyasəti, elementlərin fərdi anbarınıza köçürülməsini əngəllədi." + "message": "Bir təşkilat siyasəti, elementlərin fərdi seyfinizə köçürülməsini əngəllədi." }, "domainsTitle": { "message": "Domenlər", @@ -2609,7 +2624,7 @@ "message": "E-poçt doğrulandı" }, "emailVerificationRequiredDesc": { - "message": "Bu özəlliyi istifadə etmək üçün e-poçtunuzu doğrulamalısınız. E-poçtunuzu veb anbarında doğrulaya bilərsiniz." + "message": "Bu özəlliyi istifadə etmək üçün e-poçtunuzu doğrulamalısınız. E-poçtunuzu veb seyfdə doğrulaya bilərsiniz." }, "updatedMasterPassword": { "message": "Güncəllənmiş ana parol" @@ -2618,13 +2633,13 @@ "message": "Ana parolu güncəllə" }, "updateMasterPasswordWarning": { - "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir administrator tərəfindən dəyişdirildi. Anbara müraciət üçün indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." + "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir administrator tərəfindən dəyişdirildi. Seyfə müraciət etmək üçün onu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, "updateWeakMasterPasswordWarning": { - "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Anbara müraciət üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." + "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Seyfə müraciət üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, "tdeDisabledMasterPasswordRequired": { - "message": "Təşkilatınız, güvənli cihaz şifrələməsini sıradan çıxartdı. Anbarınıza müraciət etmək üçün lütfən ana parol təyin edin." + "message": "Təşkilatınız, güvənli cihaz şifrələməsini sıradan çıxartdı. Seyfinizə müraciət etmək üçün lütfən ana parol təyin edin." }, "resetPasswordPolicyAutoEnroll": { "message": "Avtomatik yazılma" @@ -2647,6 +2662,15 @@ "message": "Təşkilatınız bir ana parol ayarlamağı tələb edir.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "/$TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Doğrulama tələb olunur", "description": "Default title for the user verification dialog." @@ -2661,7 +2685,7 @@ "message": "Müəssisə siyasət tələbləri, vaxt bitmə seçimlərinizə tətbiq edildi" }, "vaultTimeoutPolicyInEffect": { - "message": "Təşkilatınızın siyasətləri, anbarınızın vaxt bitişinə təsir edir. Anbar vaxt bitişi üçün icazə verilən maksimum vaxt $HOURS$ saat $MINUTES$ dəqiqədir", + "message": "Təşkilatınızın siyasətləri, icazə verilən maksimum seyf bitmə vaxtını $HOURS$ saat $MINUTES$ dəqiqə olaraq ayarladı.", "placeholders": { "hours": { "content": "$1", @@ -2700,7 +2724,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Təşkilatınızın siyasətləri, anbarınızın vaxt bitişinə təsir edir. Anbar vaxt bitişi üçün icazə verilən maksimum vaxt $HOURS$ saat $MINUTES$ dəqiqədir. Anbar vaxt bitişi əməliyyatı $ACTION$ olaraq ayarlandı.", + "message": "Təşkilatınızın siyasətləri, seyfinizin bitmə vaxtına təsir edir. İcazə verilən maksimum seyf bitmə vaxtı $HOURS$ saat $MINUTES$ dəqiqədir. Seyf vaxt bitmə əməliyyatı $ACTION$ olaraq ayarlandı.", "placeholders": { "hours": { "content": "$1", @@ -2717,7 +2741,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Təşkilatınızın siyasətləri, anbar vaxt bitişi əməliyyatınızı $ACTION$ olaraq ayarladı.", + "message": "Təşkilatınızın siyasətləri, seyfinizin vaxt bitmə əməliyyatını $ACTION$ olaraq ayarladı.", "placeholders": { "action": { "content": "$1", @@ -2726,13 +2750,13 @@ } }, "vaultTimeoutTooLarge": { - "message": "Anbar vaxt bitişi, təşkilatınız tərəfindən ayarlanan məhdudiyyətləri aşır." + "message": "Seyfin bitmə vaxtı, təşkilatınız tərəfindən ayarlanan məhdudiyyətləri aşır." }, "vaultExportDisabled": { - "message": "Anbarın xaricə köçürülməsi əlçatmazdır" + "message": "Seyfin xaricə köçürülməsi əlçatmazdır" }, "personalVaultExportPolicyInEffect": { - "message": "Bir və ya daha çox təşkilat siyasəti, fərdi anbarınızı xaricə köçürməyinizi əngəlləyir." + "message": "Bir və ya daha çox təşkilat siyasəti, fərdi seyfi xaricə köçürməyinizi əngəlləyir." }, "copyCustomFieldNameInvalidElement": { "message": "Yararlı bir form elementi müəyyənləşdirilə bilmir. Bunun əvəzinə HTML-i incələməyi sınayın." @@ -2771,10 +2795,10 @@ "message": "Seansınızın vaxtı bitdi. Lütfən geri qayıdıb yenidən giriş etməyə cəhd edin." }, "exportingPersonalVaultTitle": { - "message": "Fərdi anbarın xaricə köçürülməsi" + "message": "Fərdi seyfin xaricə köçürülməsi" }, "exportingIndividualVaultDescription": { - "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək. Yalnız anbar element məlumatları xaricə köçürüləcək və əlaqələndirilmiş qoşmalar daxil edilməyəcək.", + "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş fərdi seyf elementləri xaricə köçürüləcək. Təşkilat seyf elementləri daxil edilməyəcək. Yalnız seyf element məlumatları xaricə köçürüləcək və əlaqələndirilmiş qoşmalar daxil edilməyəcək.", "placeholders": { "email": { "content": "$1", @@ -2783,10 +2807,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Təşkilat anbarını xaricə köçürmə" + "message": "Təşkilat seyfini xaricə köçürmə" }, "exportingOrganizationVaultDesc": { - "message": "Yalnız $ORGANIZATION$ ilə əlaqələndirilmiş təşkilat anbarı ixrac ediləcək. Fərdi anbardakı və digər təşkilat elementlər daxil edilmir.", + "message": "Yalnız $ORGANIZATION$ ilə əlaqələndirilmiş təşkilat seyfi xaricə köçürüləcək. Fərdi seyfdə və digər təşkilatlardakı elementlər daxil edilməyəcək.", "placeholders": { "organization": { "content": "$1", @@ -3037,7 +3061,7 @@ "message": "Barmaq izi ifadəsi" }, "fingerprintMatchInfo": { - "message": "Lütfən anbarınızın kilidinin açıq olduğuna və Barmaq izi ifadəsinin digər cihazla uyuşduğuna əmin olun." + "message": "Lütfən seyfinizin kilidinin açıq olduğuna və Barmaq izi ifadəsinin digər cihazla uyuşduğuna əmin olun." }, "resendNotification": { "message": "Bildirişi təkrar göndər" @@ -3445,7 +3469,7 @@ "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { - "message": "Yeni anbar elementi əlavə et", + "message": "Yeni seyf elementi əlavə et", "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { @@ -3453,7 +3477,7 @@ "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Yeni anbar giriş elementini əlavə et, yeni bir pəncərədə açılır", + "message": "Yeni seyf giriş elementini əlavə et, yeni bir pəncərədə açılır", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { @@ -3461,7 +3485,7 @@ "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Yeni anbar kart elementini əlavə et, yeni bir pəncərədə açılır", + "message": "Yeni seyf kart elementini əlavə et, yeni bir pəncərədə açılır", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { @@ -3469,7 +3493,7 @@ "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Yeni anbar kimlik elementini əlavə et, yeni bir pəncərədə açılır", + "message": "Yeni seyf kimlik elementini əlavə et, yeni bir pəncərədə açılır", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3639,7 +3663,7 @@ } }, "confirmVaultImport": { - "message": "Anbarın daxilə köçürülməsini təsdiqləyin" + "message": "Seyfi daxilə köçürməyi təsdiqlə" }, "confirmVaultImportDesc": { "message": "Bu fayl parolla qorunur. Məlumatları daxilə köçürmək üçün fayl parolunu daxil edin." @@ -3648,7 +3672,7 @@ "message": "Fayl parolunu təsdiqlə" }, "exportSuccess": { - "message": "Anbar datası xaricə köçürüldü" + "message": "Seyf datası xaricə köçürüldü" }, "typePasskey": { "message": "Keçid açarı" @@ -3925,7 +3949,7 @@ "message": "Bu saytın avto-doldurması üçün giriş elementini saxlayın" }, "yourVaultIsEmpty": { - "message": "Anbarınız boşdur" + "message": "Seyfiniz boşdur" }, "noItemsMatchSearch": { "message": "Axtarışınızla uyuşan heç bir element yoxdur" @@ -4480,7 +4504,7 @@ "message": "Bitwarden-in yeni bir görünüşü var!" }, "bitwardenNewLookDesc": { - "message": "Anbar vərəqindən avto-doldurma və axtarış etmə artıq daha asan və intuitivdir. Nəzər salın!" + "message": "Seyf vərəqindən avto-doldurma və axtarış etmə artıq daha asan və intuitivdir. Nəzər salın!" }, "accountActions": { "message": "Hesab fəaliyyətləri" diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 27bb1687bc7..4ce7691c943 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 8400a142403..6165f4832ed 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Присъединяване към организацията" }, + "joinOrganizationName": { + "message": "Присъединяване към $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Завършете присъединяването си към тази организация като зададете главна парола." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Показване на предложения за авт. попълване на полетата във формуляри" }, + "showInlineMenuIdentitiesLabel": { + "message": "Показване на идентичности като предложения" + }, + "showInlineMenuCardsLabel": { + "message": "Показване на карти като предложения" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Показване на предложения когато иконката е избрана" }, @@ -2647,6 +2662,15 @@ "message": "Организацията Ви изисква да зададете главна парола.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "от $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Изисква се потвърждение", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index b65edc673f2..9c6f2a5a99e 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index a7451706dbf..95f90988df4 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 13aa585d70e..ec0fd8bef7c 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Uneix-te a l'organització" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Acabeu d'unir-vos a aquesta organització establint una contrasenya mestra." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "La vostra organització requereix que establiu una contrasenya mestra.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Es requereix verificació", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 98d7336e987..bdc245099c1 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Přidat se k organizaci" }, + "joinOrganizationName": { + "message": "Připojit se k $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Dokončete připojení k této organizaci nastavením hlavního hesla." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Zobrazit návrhy automatického vyplňování v polích formuláře" }, + "showInlineMenuIdentitiesLabel": { + "message": "Zobrazit identity jako návrhy" + }, + "showInlineMenuCardsLabel": { + "message": "Zobrazit karty jako návrhy" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Zobrazit návrhy, když je vybrána ikona" }, @@ -2647,6 +2662,15 @@ "message": "Vaše organizace vyžaduje nastavení hlavního hesla.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "z $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Je vyžadováno ověření", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index ba8b5fec1c8..e5b77b001ec 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 54c1bc41627..8ec111967fa 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Bliv medlem af organisation" }, + "joinOrganizationName": { + "message": "Bliv nedlem af $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Færdiggør tilmeldingen til denne organisation ved at opsætte en hovedadgangskode." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Vis autoudfyld-menu i formularfelter" }, + "showInlineMenuIdentitiesLabel": { + "message": "Vis identiteter som forslag" + }, + "showInlineMenuCardsLabel": { + "message": "Vis kort som forslag" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Vis forslag, når ikonet vælges" }, @@ -2647,6 +2662,15 @@ "message": "Organisationen kræver, at der oprettes en hovedadgangskode.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "ud af $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Bekræftelse kræves", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index b61645180c4..a28c8f947b6 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Organisation beitreten" }, + "joinOrganizationName": { + "message": "$ORGANIZATIONNAME$ beitreten", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Schließe den Beitritt zu dieser Organisation ab, indem du ein Master-Passwort festlegst." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Vorschläge zum Auto-Ausfüllen in Formularfeldern anzeigen" }, + "showInlineMenuIdentitiesLabel": { + "message": "Zeige Identitäten als Vorschläge" + }, + "showInlineMenuCardsLabel": { + "message": "Zeige Karten als Vorschläge" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Vorschläge anzeigen, wenn Symbol ausgewählt ist" }, @@ -2647,6 +2662,15 @@ "message": "Deine Organisation verlangt, dass du ein Master-Passwort festlegen musst.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "von $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verifizierung erforderlich", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 11f57667b4f..3318e057024 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Συμμετοχή στον οργανισμό" }, + "joinOrganizationName": { + "message": "Συμμετοχή στο $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Ολοκληρώστε τη συμμετοχή σας σε αυτόν τον οργανισμό ορίζοντας έναν κύριο κωδικό πρόσβασης." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Εμφάνιση μενού αυτόματης συμπλήρωσης στα πεδία της φόρμας" }, + "showInlineMenuIdentitiesLabel": { + "message": "Εμφάνιση ταυτοτήτων ως προτάσεις" + }, + "showInlineMenuCardsLabel": { + "message": "Εμφάνιση καρτών ως προτάσεις" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Εμφάνιση προτάσεων όταν το εικονίδιο είναι επιλεγμένο" }, @@ -2647,6 +2662,15 @@ "message": "Ο οργανισμός σας απαιτεί να ορίσετε έναν κύριο κωδικό πρόσβασης.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "από $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Απαιτείται επαλήθευση", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 0a1c2bf2d1e..026d3b535ca 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2449,8 +2464,8 @@ "message": "Optionally require a password for users to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendPasswordDescV2": { - "message": "Require this password to view the Send.", + "sendPasswordDescV3": { + "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { @@ -4479,9 +4494,15 @@ "itemLocation": { "message": "Item Location" }, + "fileSend": { + "message": "File Send" + }, "fileSends": { "message": "File Sends" }, + "textSend": { + "message": "Text Send" + }, "textSends": { "message": "Text Sends" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index fe295c7c249..4d295f1c9c5 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organisation" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organisation by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organisation requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 0e938644fdd..6104f15f630 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organisation" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organisation by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organisation requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 4884a76ba58..0eadf338669 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Incorporarse a la organización" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Termine de unirse a esta organización estableciendo una contraseña maestra." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Su organización requiere que establezca una contraseña maestra.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Se requiere verificación", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index b8121aa2de9..83562adbb75 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Liitu organisatsiooniga" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Lõpeta organisatsiooniga liitumine määrates ülemparool." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 6563a5397d2..39c1d323627 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 93dd5f98200..3d7ea582616 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "تایید لازم است", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 801926eb345..6f325a2f6a9 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Liity organisaatioon" }, + "joinOrganizationName": { + "message": "Liity organisaatioon $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Viimeistele liittyminen organisaatioon asettamalla pääsalasana." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Näytä automaattitäytön ehdotukset lomakekentissä" }, + "showInlineMenuIdentitiesLabel": { + "message": "Näytä identiteetit ehdotuksina" + }, + "showInlineMenuCardsLabel": { + "message": "Näytä kortit ehdotuksina" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Näytä ehdotukset kun kuvaketta painetaan" }, @@ -2499,11 +2514,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "Tämän linkin välityksellä Send on kenen tahansa avattavissa seuraavan tunnin ajan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "Tämän linkin välityksellä Send on kenen tahansa avattavissa seuraavien $HOURS$ tunnin ajan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2513,11 +2528,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "Tämän linkin välityksellä Send on kenen tahansa avattavissa seuraavan päivän ajan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Tämän linkin välityksellä Send on kenen tahansa avattavissa seuraavien $DAYS$ päivän ajan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2647,6 +2662,15 @@ "message": "Organisaatiosi edellyttää, että asetat pääsalasanan.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Vahvistus vaaditaan", "description": "Default title for the user verification dialog." @@ -3681,7 +3705,7 @@ "message": "Tälle sivustolle sopivia kirjautumistietoja ei ole" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Hae tai tallenna pääsyavain uutena kirjautumistietona" }, "confirm": { "message": "Vahvista" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 46a353fc765..80e8361cb34 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index eb09e65cc4a..aa18348cfda 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Rejoindre l'organisation" }, + "joinOrganizationName": { + "message": "Rejoindre $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Terminer de rejoindre cette organisation en configurant un mot de passe principal." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Afficher les suggestions de saisie automatique dans les champs d'un formulaire" }, + "showInlineMenuIdentitiesLabel": { + "message": "Afficher les identités sous forme de suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Afficher les cartes de paiement sous forme de suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Afficher les suggestions lorsque l'icône est sélectionnée" }, @@ -2647,6 +2662,15 @@ "message": "Votre organisation exige que vous définissiez un mot de passe principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "sur $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Vérification requise", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 539631cb9e1..139cb2ac4bf 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 6fa90bfd51b..8a97a47966f 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "הצטרפות אל $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "הארגון שלך דורש ממך להגדיר סיסמה ראשית.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index b349f2c3e99..8d4d1521988 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "कुल $TOTAL$ में से", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index fbd497a4c02..4767362352a 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Pridruži se organizaciji" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Dovrši pridruživanje organizaciji postavljanjem glavne lozinke." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Prikaži prijedloge auto-ispune na poljima obrazaca" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Prikaži prijedloge kada je odabrana ikona" }, @@ -2647,6 +2662,15 @@ "message": "Tvoja organizacija zahtijeva da postaviš glavnu lozinku.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Potrebna je potvrda", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 26f09ff7e0d..ef6ba5a859e 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Csatlakozás szervezethez" }, + "joinOrganizationName": { + "message": "Csatlakozás: $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Fejezzük be a szervezethez csatlakozást egy mesterjelszó beállításával." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Az identitások megjelenítése javaslatként" + }, + "showInlineMenuCardsLabel": { + "message": "A kártyák megjelenítése javaslatként" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "A szervezet megköveteli egy mesterjelszó beállítását.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "/ $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Ellenőrzés szükséges", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index b339e210323..8181bdea9c0 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index df23403c9a2..3b912c2e039 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Unisciti all'organizzazione" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Termina l'adesione a questa organizzazione impostando una password principale." }, @@ -147,13 +156,13 @@ "message": "Riempimento automatico" }, "autoFillLogin": { - "message": "Autocompletamento login" + "message": "Riempi automaticamente login" }, "autoFillCard": { - "message": "Autocompletamento carta" + "message": "Riempi automaticamente carta" }, "autoFillIdentity": { - "message": "Autocompletamento identità" + "message": "Riempi automaticamente identità" }, "generatePasswordCopied": { "message": "Genera password e copiala" @@ -207,7 +216,7 @@ "message": "Enter your account email address and your password hint will be sent to you" }, "passwordHint": { - "message": "Suggerimento password" + "message": "Suggerimento per la password" }, "enterEmailToGetHint": { "message": "Inserisci l'indirizzo email del tuo account per ricevere il suggerimento per la password principale." @@ -782,10 +791,10 @@ "message": "Rendi la 2FA facile" }, "totpHelper": { - "message": "Bitwarden può memorizzare e autocompletare codici di verifica 2FA. Copia e incolla la chiave in questo campo." + "message": "Bitwarden può memorizzare e riempire automaticamente i codici di verifica 2FA. Copia e incolla la chiave in questo campo." }, "totpHelperWithCapture": { - "message": "Bitwarden può memorizzare e autocompletare codici di verifica 2FA. Selezionare l'icona della fotocamera per creare uno screenshot del codice QR dell'autenticatore di questo sito web, oppure copia e incolla la chiave in questo campo." + "message": "Bitwarden può memorizzare e riempire automaticamente i codici di verifica 2FA. Selezionare l'icona della fotocamera per creare uno screenshot del codice QR dell'autenticatore di questo sito web, oppure copia e incolla la chiave in questo campo." }, "learnMoreAboutAuthenticators": { "message": "Ulteriori informazioni sugli autenticatori" @@ -944,7 +953,7 @@ "message": "Mostra le carte nella sezione Scheda" }, "showCardsCurrentTabDesc": { - "message": "Mostra le carte nella sezione Scheda per un riempimento automatico più facile." + "message": "Mostra le carte nella sezione Scheda per riempirle automaticamente." }, "showIdentitiesInVaultView": { "message": "Mostra le identità come suggerimenti di riempimento automatico nella vista cassaforte" @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Mostra suggerimenti di riempimento automatico nei campi del modulo" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Mostra suggerimenti quando l'icona è selezionata" }, @@ -1436,13 +1451,13 @@ "message": "Riempi automaticamente al caricamento della pagina" }, "enableAutoFillOnPageLoad": { - "message": "Abilita l'auto-completamento al caricamento della pagina" + "message": "Riempi automaticamente al caricamento della pagina" }, "enableAutoFillOnPageLoadDesc": { "message": "Se sono rilevati campi di login, riempili automaticamente quando la pagina si carica." }, "autofillOnPageLoadWarning": { - "message": "$OPENTAG$Attenzione:$CLOSETAG$ Siti Web compromessi o non attendibili possono sfruttare l'auto-riempimento al caricamento della pagina.", + "message": "$OPENTAG$Attenzione:$CLOSETAG$ Siti Web compromessi o non attendibili possono sfruttare il riempimento automatico al caricamento della pagina.", "placeholders": { "openTag": { "content": "$1", @@ -2046,7 +2061,7 @@ "message": "Elemento riempito automaticamente e URI salvato" }, "autoFillSuccess": { - "message": "Elemento riempito automaticamente" + "message": "Elemento riempito automaticamente " }, "insecurePageWarning": { "message": "Attenzione: questa è una pagina HTTP non protetta, e tutte le informazioni che invii potrebbero essere viste e modificate da altri. Questo login è stato originariamente salvato su una pagina sicura (HTTPS)." @@ -2647,6 +2662,15 @@ "message": "La tua organizzazione ti obbliga di impostare di una password principale.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verifica necessaria", "description": "Default title for the user verification dialog." @@ -2771,7 +2795,7 @@ "message": "La tua sessione è scaduta. Torna indietro e prova ad accedere di nuovo." }, "exportingPersonalVaultTitle": { - "message": "Esportazione cassaforte personale" + "message": "Esportando cassaforte individuale" }, "exportingIndividualVaultDescription": { "message": "Solo gli elementi della cassaforte personale associati a $EMAIL$ saranno esportati. Gli elementi della cassaforte dell'organizzazione non saranno inclusi. Solo le informazioni sugli elementi della cassaforte saranno esportate e non includeranno gli allegati.", @@ -2986,7 +3010,7 @@ "message": "per ritornare alle impostazioni preconfigurate" }, "serverVersion": { - "message": "Versione Server" + "message": "Versione server" }, "selfHostedServer": { "message": "self-hosted" @@ -3082,7 +3106,7 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Le politiche della tua organizzazione hanno abilitato l'autocompletamento al caricamento della pagina." + "message": "Le politiche della tua organizzazione hanno abilitato il riempimento automatico al caricamento della pagina." }, "howToAutofill": { "message": "Come riempire automaticamente" @@ -3106,22 +3130,22 @@ "message": "Impostazioni di riempimento automatico" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Scorciatoia auto-riempimento" + "message": "Scorciatoia del riempimento automatico" }, "autofillKeyboardShortcutUpdateLabel": { "message": "Cambia scorciatoia" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Gestisci scorciatoia" + "message": "Gestisci scorciatoie" }, "autofillShortcut": { "message": "Scorciatoia da tastiera per riempire automaticamente" }, "autofillLoginShortcutNotSet": { - "message": "Non è stata impostata nessuna scorciatoia per il riempimento automatico. Cambiala nelle impostazioni del browser." + "message": "Non è stata impostata nessuna scorciatoia per il riempimento automatico. Aggiungila dalle impostazioni del browser." }, "autofillLoginShortcutText": { - "message": "La scorciatoia per l'auto-riempimento è $COMMAND$.\nGestisci tutte le scorciatoie dalle impostazioni del browser.", + "message": "La scorciatoia per il riempimento automatico è $COMMAND$. Gestisci tutte le scorciatoie dalle impostazioni del browser.", "placeholders": { "command": { "content": "$1", @@ -3922,7 +3946,7 @@ "message": "Suggerimenti per il riempimento automatico" }, "autofillSuggestionsTip": { - "message": "Salva un elemento login per questo sito da riempire automaticamente" + "message": "Salva un elemento di accesso per questo sito da riempire automaticamente" }, "yourVaultIsEmpty": { "message": "La tua cassaforte è vuota" @@ -4319,7 +4343,7 @@ "message": "Usa le caselle di controllo se vuoi riempire automaticamente la casella di controllo di un modulo, come una email da ricordare" }, "linkedHelpText": { - "message": "Utilizzare un campo collegato quando si verificano problemi di riempimento automatico per un sito web specifico." + "message": "Usa un campo collegato quando si verificano problemi di riempimento automatico per un sito web specifico." }, "linkedLabelHelpText": { "message": "Inserisci l'id html del campo, il nome, l'aria-label o il segnaposto." diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 669ddd63df2..b7da0c89584 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "組織に参加" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "マスターパスワードを設定して、この組織への参加を完了します。" }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "フォームフィールドに自動入力の候補を表示する" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "アイコンが選択されているときに候補を表示する" }, @@ -2647,6 +2662,15 @@ "message": "あなたの組織では、マスターパスワードの設定を義務付けています。", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": " / $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "認証が必要です", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 167251e99c2..3c487c9dfa1 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -35,10 +35,10 @@ "message": "დახურვა" }, "submit": { - "message": "დადასტურება" + "message": "გადაცემა" }, "emailAddress": { - "message": "ელ-ფოსტა" + "message": "ელფოსტის მისამართი" }, "masterPass": { "message": "Master password" @@ -71,14 +71,23 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, "tab": { - "message": "Tab" + "message": "ჩანართი" }, "vault": { - "message": "Vault" + "message": "საცავი" }, "myVault": { "message": "My vault" @@ -90,7 +99,7 @@ "message": "ხელსაწყოები" }, "settings": { - "message": "პარამეტრები" + "message": "მორგება" }, "currentTab": { "message": "Current tab" @@ -114,7 +123,7 @@ "message": "უსაფრთხოების კოდის კოპირება" }, "copyName": { - "message": "Copy name" + "message": "სახელის კოპირება" }, "copyCompany": { "message": "Copy company" @@ -144,7 +153,7 @@ "message": "Copy notes" }, "autoFill": { - "message": "თვითშევსება" + "message": "ავტომატური შევსება" }, "autoFillLogin": { "message": "Autofill login" @@ -192,7 +201,7 @@ "message": "ავტორიზაციის დამატება" }, "addItem": { - "message": "Add item" + "message": "ელემენტის დამატება" }, "accountEmail": { "message": "Account email" @@ -228,7 +237,7 @@ "message": "კოდი გაიგზავნა" }, "verificationCode": { - "message": "ერთჯერადი კოდი" + "message": "გადამოწმების კოდი" }, "confirmIdentity": { "message": "Confirm your identity to continue." @@ -269,7 +278,7 @@ "message": "ორსაფეხურიანი ავტორიზაცია" }, "logOut": { - "message": "გამოსვლა" + "message": "სისტემიდან გასვლა" }, "aboutBitwarden": { "message": "About Bitwarden" @@ -317,7 +326,7 @@ "message": "შენახვა" }, "move": { - "message": "Move" + "message": "გადატანა" }, "addFolder": { "message": "საქაღალდის დამატება" @@ -326,13 +335,13 @@ "message": "სახელი" }, "editFolder": { - "message": "საქაღალდის რედაქტირება" + "message": "საქაღალდის ჩასწორება" }, "newFolder": { - "message": "New folder" + "message": "ახალი საქაღალდე" }, "folderName": { - "message": "Folder name" + "message": "საქაღალდის სახელი" }, "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" @@ -356,7 +365,7 @@ "message": "There are no folders to list." }, "helpFeedback": { - "message": "დახმარება & გამოხმაურება" + "message": "დახმარება და უკუკავშირი" }, "helpCenter": { "message": "Bitwarden Help center" @@ -374,13 +383,13 @@ "message": "Sync vault now" }, "lastSync": { - "message": "Last sync:" + "message": "ბოლო სინქი:" }, "passGen": { "message": "Password generator" }, "generator": { - "message": "Generator", + "message": "გენერატორი", "description": "Short for 'credential generator'." }, "passGenInfo": { @@ -393,22 +402,22 @@ "message": "Import items" }, "select": { - "message": "მონიშვნა" + "message": "არჩევა" }, "generatePassword": { - "message": "Generate password" + "message": "პაროლის გენერირება" }, "regeneratePassword": { "message": "Regenerate password" }, "options": { - "message": "პარამეტრები" + "message": "მორგება" }, "length": { "message": "სიგრძე" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "პაროლის მინიმალური სიგრძე" }, "uppercase": { "message": "Uppercase (A-Z)", @@ -427,7 +436,7 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "include": { - "message": "Include", + "message": "ჩართვა", "description": "Card header for password generator include block" }, "uppercaseDescription": { @@ -435,7 +444,7 @@ "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { - "message": "A-Z", + "message": "ა-ჰ", "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { @@ -469,7 +478,7 @@ "message": "Word separator" }, "capitalize": { - "message": "დიდი ასოთი აღნიშვნა", + "message": "მაღალ რეგისტრში გადაყვანა", "description": "Make the first letter of a work uppercase." }, "includeNumber": { @@ -497,10 +506,10 @@ "message": "Search vault" }, "edit": { - "message": "შეცვლა" + "message": "ჩასწორება" }, "view": { - "message": "ნახვა" + "message": "ხედი" }, "noItemsInList": { "message": "There are no items to list." @@ -518,13 +527,13 @@ "message": "Authenticator secret" }, "passphrase": { - "message": "Passphrase" + "message": "საკვანძო სიტყვა" }, "favorite": { "message": "რჩეული" }, "unfavorite": { - "message": "Unfavorite" + "message": "რჩეულებიდან წაშლა" }, "itemAddedToFavorites": { "message": "Item added to favorites" @@ -533,28 +542,28 @@ "message": "Item removed from favorites" }, "notes": { - "message": "Notes" + "message": "შენიშვნები" }, "privateNote": { - "message": "Private note" + "message": "პირადი მინაწერი" }, "note": { - "message": "Note" + "message": "შენიშვნა" }, "editItem": { - "message": "Edit item" + "message": "ჩანაწერის ჩასწორება" }, "folder": { "message": "საქაღალდე" }, "deleteItem": { - "message": "Delete item" + "message": "ჩანაწერის წაშლა" }, "viewItem": { "message": "View item" }, "launch": { - "message": "Launch" + "message": "გაშვება" }, "launchWebsite": { "message": "Launch website" @@ -563,7 +572,7 @@ "message": "ვებგვერდი" }, "toggleVisibility": { - "message": "Toggle visibility" + "message": "ხილულობის გადართვა" }, "manage": { "message": "მართვა" @@ -587,7 +596,7 @@ "message": "Vault timeout" }, "otherOptions": { - "message": "Other options" + "message": "სხვა პარამეტრები" }, "rateExtension": { "message": "Rate the extension" @@ -614,7 +623,7 @@ "message": "or" }, "unlock": { - "message": "გახსნა" + "message": "განბლოკვა" }, "loggedInAsOn": { "message": "Logged in as $EMAIL$ on $HOSTNAME$.", @@ -636,7 +645,7 @@ "message": "Vault timeout" }, "vaultTimeout1": { - "message": "Timeout" + "message": "მოლოდინის ვადა" }, "lockNow": { "message": "Lock now" @@ -645,7 +654,7 @@ "message": "Lock all" }, "immediately": { - "message": "დაუყონებლივ" + "message": "დაუყოვნებლივ" }, "tenSeconds": { "message": "10 წამი" @@ -705,10 +714,10 @@ "message": "დაფიქსირდა შეცდომა" }, "emailRequired": { - "message": "ელ-ფოსტის მისამართი აუცილებელია." + "message": "ელფოსტის მისამართი აუცილებელია." }, "invalidEmail": { - "message": "არასწორი ელ-ფოსტის მისამართი." + "message": "არასწორი ელფოსტის მისამართი." }, "masterPasswordRequired": { "message": "Master password is required." @@ -748,13 +757,13 @@ "message": "We've sent you an email with your master password hint." }, "verificationCodeRequired": { - "message": "ერთჯერადი კოდი აუცილებელია." + "message": "გადამოწმების კოდი აუცილებელია." }, "webauthnCancelOrTimeout": { "message": "The authentication was cancelled or took too long. Please try again." }, "invalidVerificationCode": { - "message": "Invalid verification code" + "message": "არასწორი გადამოწმების კოდი" }, "valueCopied": { "message": "$VALUE$ copied", @@ -794,7 +803,7 @@ "message": "Copy Authenticator key (TOTP)" }, "loggedOut": { - "message": "Logged out" + "message": "გამოხვედით" }, "loggedOutDesc": { "message": "You have been logged out of your account." @@ -803,7 +812,7 @@ "message": "Your login session has expired." }, "logIn": { - "message": "Log in" + "message": "შესვლა" }, "restartRegistration": { "message": "Restart registration" @@ -818,10 +827,10 @@ "message": "You may already have an account" }, "logOutConfirmation": { - "message": "Are you sure you want to log out?" + "message": "დარწმუნებული ბრძანდებით, რომ გნებავთ, გახვიდეთ?" }, "yes": { - "message": "Yes" + "message": "კი" }, "no": { "message": "არა" @@ -866,7 +875,7 @@ "message": "Syncing failed" }, "passwordCopied": { - "message": "Password copied" + "message": "პაროლი დაკოპირდა" }, "uri": { "message": "URI" @@ -882,10 +891,10 @@ } }, "newUri": { - "message": "New URI" + "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": { @@ -916,13 +925,13 @@ "message": "Search folder" }, "searchCollection": { - "message": "Search collection" + "message": "კოლექციაში ძებნა" }, "searchType": { "message": "Search type" }, "noneFolder": { - "message": "No folder", + "message": "საქაღალდის გარეშე", "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { @@ -956,7 +965,7 @@ "message": "List identity items on the Tab page for easy autofill." }, "clearClipboard": { - "message": "Clear clipboard", + "message": "ბუფერის გასუფთავება", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { @@ -967,7 +976,7 @@ "message": "Should Bitwarden remember this password for you?" }, "notificationAddSave": { - "message": "Save" + "message": "შენახვა" }, "enableChangedPasswordNotification": { "message": "Ask to update existing login" @@ -988,16 +997,16 @@ "message": "Do you want to update this password in Bitwarden?" }, "notificationChangeSave": { - "message": "Update" + "message": "განახლება" }, "notificationUnlockDesc": { "message": "Unlock your Bitwarden vault to complete the autofill request." }, "notificationUnlock": { - "message": "Unlock" + "message": "განბლოკვა" }, "additionalOptions": { - "message": "Additional options" + "message": "დამატებითი პარამეტრები" }, "enableContextMenuItem": { "message": "Show context menu options" @@ -1016,7 +1025,7 @@ "message": "Choose the default way that URI match detection is handled for logins when performing actions such as autofill." }, "theme": { - "message": "Theme" + "message": "თემა" }, "themeDesc": { "message": "Change the application's color theme." @@ -1025,15 +1034,15 @@ "message": "Change the application's color theme. Applies to all logged in accounts." }, "dark": { - "message": "Dark", + "message": "ბნელი", "description": "Dark color" }, "light": { - "message": "Light", + "message": "ღია", "description": "Light color" }, "solarizedDark": { - "message": "Solarized dark", + "message": "სოლარიზებული მუქი", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { @@ -1061,7 +1070,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": "გატანის ტიპი" }, "accountRestricted": { "message": "Account restricted" @@ -1070,7 +1079,7 @@ "message": "“File password” and “Confirm file password“ do not match." }, "warning": { - "message": "WARNING", + "message": "გაფრთხილება", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { @@ -1089,7 +1098,7 @@ "message": "Enter your master password to export your vault data." }, "shared": { - "message": "Shared" + "message": "გაზიარებული" }, "bitwardenForBusinessPageDesc": { "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." @@ -1098,7 +1107,7 @@ "message": "Move to organization" }, "share": { - "message": "Share" + "message": "გაზიარება" }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", @@ -1117,7 +1126,7 @@ "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, "learnMore": { - "message": "Learn more" + "message": "გაიგეთ მეტი" }, "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" @@ -1129,7 +1138,7 @@ "message": "Copy verification code" }, "attachments": { - "message": "Attachments" + "message": "დანართი" }, "deleteAttachment": { "message": "Delete attachment" @@ -1150,13 +1159,13 @@ "message": "Attachment saved" }, "file": { - "message": "File" + "message": "ფაილი" }, "fileToShare": { "message": "File to share" }, "selectFile": { - "message": "Select a file" + "message": "აირჩიეთ ფაილი" }, "maxFileSize": { "message": "Maximum file size is 500 MB." @@ -1282,7 +1291,7 @@ } }, "rememberMe": { - "message": "Remember me" + "message": "დამიმახსოვრე" }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" @@ -1300,7 +1309,7 @@ "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." }, "webAuthnNewTabOpen": { - "message": "Open new tab" + "message": "ახალი ჩანართის გახსნა" }, "webAuthnAuthenticate": { "message": "Authenticate WebAuthn" @@ -1324,7 +1333,7 @@ "message": "Recovery code" }, "authenticatorAppTitle": { - "message": "Authenticator app" + "message": "ავთენტიკატორი აპი" }, "authenticatorAppDescV2": { "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", @@ -1351,7 +1360,7 @@ "message": "Use any WebAuthn compatible security key to access your account." }, "emailTitle": { - "message": "Email" + "message": "ელ-ფოსტა" }, "emailDescV2": { "message": "Enter a code sent to your email." @@ -1378,7 +1387,7 @@ "message": "For advanced users. You can specify the base URL of each service independently." }, "baseUrl": { - "message": "Server URL" + "message": "სერვერის URL" }, "apiUrl": { "message": "API server URL" @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -1421,7 +1436,7 @@ "message": "Edit browser settings." }, "autofillOverlayVisibilityOff": { - "message": "Off", + "message": "გამორთული", "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { @@ -1506,10 +1521,10 @@ "message": "Custom fields" }, "copyValue": { - "message": "Copy value" + "message": "დააკოპირეთ მნიშვნელობა" }, "value": { - "message": "Value" + "message": "მნიშვნელობა" }, "newCustomField": { "message": "New custom field" @@ -1518,19 +1533,19 @@ "message": "Drag to sort" }, "cfTypeText": { - "message": "Text" + "message": "ტექსტი" }, "cfTypeHidden": { - "message": "Hidden" + "message": "დამალული" }, "cfTypeBoolean": { - "message": "Boolean" + "message": "ბულევი" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "ჩასართავი" }, "cfTypeLinked": { - "message": "Linked", + "message": "მიბმული", "description": "This describes a field that is 'linked' (tied) to another field." }, "linkedValue": { @@ -1562,10 +1577,10 @@ "message": "Cardholder name" }, "number": { - "message": "Number" + "message": "რიცხვი" }, "brand": { - "message": "Brand" + "message": "სავაჭრო მარკა" }, "expirationMonth": { "message": "Expiration month" @@ -1574,43 +1589,43 @@ "message": "Expiration year" }, "expiration": { - "message": "Expiration" + "message": "ვადა" }, "january": { - "message": "January" + "message": "იანვარი" }, "february": { - "message": "February" + "message": "თებერვალი" }, "march": { - "message": "March" + "message": "მარტი" }, "april": { - "message": "April" + "message": "აპრილი" }, "may": { - "message": "May" + "message": "მაისი" }, "june": { - "message": "June" + "message": "ივნისი" }, "july": { - "message": "July" + "message": "ივლისი" }, "august": { - "message": "August" + "message": "აგვისტო" }, "september": { - "message": "September" + "message": "სექტემბერი" }, "october": { - "message": "October" + "message": "ოქტომბერი" }, "november": { - "message": "November" + "message": "ნოემბერი" }, "december": { - "message": "December" + "message": "დეკემბერი" }, "securityCode": { "message": "Security code" @@ -1619,40 +1634,40 @@ "message": "ex." }, "title": { - "message": "Title" + "message": "სათაური" }, "mr": { - "message": "Mr" + "message": "ბატონი" }, "mrs": { - "message": "Mrs" + "message": "ქალბატონი" }, "ms": { - "message": "Ms" + "message": "ქალბატონი" }, "dr": { - "message": "Dr" + "message": "დოქტორი" }, "mx": { "message": "Mx" }, "firstName": { - "message": "First name" + "message": "სახელი" }, "middleName": { - "message": "Middle name" + "message": "შუა სახელი" }, "lastName": { - "message": "Last name" + "message": "გვარი" }, "fullName": { - "message": "Full name" + "message": "სრული სახელი" }, "identityName": { "message": "Identity name" }, "company": { - "message": "Company" + "message": "კომპანია" }, "ssn": { "message": "Social Security number" @@ -1664,13 +1679,13 @@ "message": "License number" }, "email": { - "message": "Email" + "message": "ელ-ფოსტა" }, "phone": { - "message": "Phone" + "message": "ტელეფონი" }, "address": { - "message": "Address" + "message": "მისამართი" }, "address1": { "message": "Address 1" @@ -1685,19 +1700,19 @@ "message": "City / Town" }, "stateProvince": { - "message": "State / Province" + "message": "რეგიონი/მხარე" }, "zipPostalCode": { "message": "Zip / Postal code" }, "country": { - "message": "Country" + "message": "ქვეყანა" }, "type": { - "message": "Type" + "message": "ტიპი" }, "typeLogin": { - "message": "Login" + "message": "შესვლა" }, "typeLogins": { "message": "Logins" @@ -1706,10 +1721,10 @@ "message": "Secure note" }, "typeCard": { - "message": "Card" + "message": "ბარათი" }, "typeIdentity": { - "message": "Identity" + "message": "იდენტიფიკაცია" }, "newItemHeader": { "message": "New $TYPE$", @@ -1742,10 +1757,10 @@ "message": "Password history" }, "back": { - "message": "Back" + "message": "უკან" }, "collections": { - "message": "Collections" + "message": "კოლექციები" }, "nCollections": { "message": "$COUNT$ collections", @@ -1757,19 +1772,19 @@ } }, "favorites": { - "message": "Favorites" + "message": "სანიშნეები" }, "popOutNewWindow": { "message": "Pop out to a new window" }, "refresh": { - "message": "Refresh" + "message": "განახლება" }, "cards": { - "message": "Cards" + "message": "კარტი" }, "identities": { - "message": "Identities" + "message": "იდენტიფიკატორები" }, "logins": { "message": "Logins" @@ -1778,7 +1793,7 @@ "message": "Secure notes" }, "clear": { - "message": "Clear", + "message": "გაწმენდა", "description": "To clear something out. example: To clear browser history." }, "checkPassword": { @@ -1805,21 +1820,21 @@ "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain name", + "message": "დომენის სახელი", "description": "Domain name. Ex. website.com" }, "host": { - "message": "Host", + "message": "ჰოსტი", "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { - "message": "Exact" + "message": "ზუსტი" }, "startsWith": { - "message": "Starts with" + "message": "იწყება" }, "regEx": { - "message": "Regular expression", + "message": "რეგულარულ გამოსახულება", "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { @@ -1842,20 +1857,20 @@ "description": "The URI of one of the current open tabs in the browser." }, "organization": { - "message": "Organization", + "message": "ორგანიზაცია", "description": "An entity of multiple related people (ex. a team or business organization)." }, "types": { - "message": "Types" + "message": "ტიპები" }, "allItems": { - "message": "All items" + "message": "ყველა ჩანაწერი" }, "noPasswordsInList": { "message": "There are no passwords to list." }, "clearHistory": { - "message": "Clear history" + "message": "პაროლების ისტორია" }, "noPasswordsToShow": { "message": "No passwords to show" @@ -1864,17 +1879,17 @@ "message": "You haven't generated a password recently" }, "remove": { - "message": "Remove" + "message": "წაშლა" }, "default": { - "message": "Default" + "message": "ნაგულისხმევი" }, "dateUpdated": { - "message": "Updated", + "message": "განახლებულია", "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "შექმნის თარიღი", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -1891,21 +1906,21 @@ "message": "There are no collections to list." }, "ownership": { - "message": "Ownership" + "message": "მფლობელობა" }, "whoOwnsThisItem": { "message": "Who owns this item?" }, "strong": { - "message": "Strong", + "message": "ძლიერი", "description": "ex. A strong password. Scale: Weak -> Good -> Strong" }, "good": { - "message": "Good", + "message": "კარგი", "description": "ex. A good password. Scale: Weak -> Good -> Strong" }, "weak": { - "message": "Weak", + "message": "სუსტი", "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { @@ -1915,7 +1930,7 @@ "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" }, "pin": { - "message": "PIN", + "message": "PIN კოდი", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { @@ -1967,7 +1982,7 @@ "message": "Clone item" }, "clone": { - "message": "Clone" + "message": "კლონი" }, "passwordGeneratorPolicyInEffect": { "message": "One or more organization policies are affecting your generator settings." @@ -2002,11 +2017,11 @@ "message": "Timeout action" }, "lock": { - "message": "Lock", + "message": "ჩაკეტვა", "description": "Verb form: to make secure or inaccessible by" }, "trash": { - "message": "Trash", + "message": "ნაგავი", "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { @@ -2028,7 +2043,7 @@ "message": "Item restored" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "უკვე გაქვთ ანგარიში?" }, "vaultTimeoutLogOutConfirmation": { "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" @@ -2124,7 +2139,7 @@ "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "გამოწერის გაუქმება" }, "atAnyTime": { "message": "at any time." @@ -2142,16 +2157,16 @@ "message": "Terms of Service and Privacy Policy have not been acknowledged." }, "termsOfService": { - "message": "Terms of Service" + "message": "მომსახურების პირობები" }, "privacyPolicy": { - "message": "Privacy Policy" + "message": "კონფიდენციალობის პოლიტიკა" }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." }, "ok": { - "message": "Ok" + "message": "დიახ" }, "errorRefreshingAccessToken": { "message": "Access Token Refresh Error" @@ -2253,7 +2268,7 @@ "message": "An organization policy has blocked importing items into your individual vault." }, "domainsTitle": { - "message": "Domains", + "message": "დომენები", "description": "A category title describing the concept of web domains" }, "excludedDomains": { @@ -2304,7 +2319,7 @@ } }, "send": { - "message": "Send", + "message": "გაგზავნა", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { @@ -2320,13 +2335,13 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { - "message": "Text" + "message": "ტექსტი" }, "sendTypeTextToShare": { "message": "Text to share" }, "sendTypeFile": { - "message": "File" + "message": "ფაილი" }, "allSends": { "message": "All Sends", @@ -2340,26 +2355,26 @@ "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "expired": { - "message": "Expired" + "message": "ვადაგასულია" }, "pendingDeletion": { - "message": "Pending deletion" + "message": "ელოდება წაშლას" }, "passwordProtected": { "message": "Password protected" }, "copyLink": { - "message": "Copy link" + "message": "Ბმულის კოპირება" }, "copySendLink": { "message": "Copy Send link", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { - "message": "Remove Password" + "message": "პაროლის წაშლა" }, "delete": { - "message": "Delete" + "message": "წაშლა" }, "removedPassword": { "message": "Password removed" @@ -2373,7 +2388,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { - "message": "Disabled" + "message": "გამორთული" }, "removePasswordConfirmation": { "message": "Are you sure you want to remove the password?" @@ -2417,14 +2432,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Expiration date" + "message": "ვადა" }, "expirationDateDesc": { "message": "If set, access to this Send will expire on the specified date and time.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "oneDay": { - "message": "1 day" + "message": "1 დღე" }, "days": { "message": "$DAYS$ days", @@ -2436,7 +2451,7 @@ } }, "custom": { - "message": "Custom" + "message": "განსხვავებული" }, "maximumAccessCount": { "message": "Maximum Access Count" @@ -2480,7 +2495,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "New password" + "message": "ახალი პაროლი" }, "sendDisabled": { "message": "Send removed", @@ -2552,7 +2567,7 @@ "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." }, "popOut": { - "message": "Pop out" + "message": "გატანა" }, "sendFileCalloutHeader": { "message": "Before you start" @@ -2562,7 +2577,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" }, "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", + "message": "აქ დააწკაპუნეთ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" }, "sendFirefoxCustomDatePopoutMessage3": { @@ -2606,7 +2621,7 @@ "message": "Email verification required" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ელფოსტა გადამოწმებულია" }, "emailVerificationRequiredDesc": { "message": "You must verify your email to use this feature. You can verify your email in the web vault." @@ -2633,7 +2648,7 @@ "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." }, "selectFolder": { - "message": "Select folder..." + "message": "აირჩიეთ საქაღალდე..." }, "noFoldersFound": { "message": "No folders found", @@ -2647,15 +2662,24 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." }, "hours": { - "message": "Hours" + "message": "საათი" }, "minutes": { - "message": "Minutes" + "message": "წუთი" }, "vaultTimeoutPolicyAffectingOptions": { "message": "Enterprise policy requirements have been applied to your timeout options" @@ -2795,7 +2819,7 @@ } }, "error": { - "message": "Error" + "message": "შეცდომა" }, "regenerateUsername": { "message": "Regenerate username" @@ -2820,7 +2844,7 @@ "message": "Use your domain's configured catch-all inbox." }, "random": { - "message": "Random" + "message": "შემთხვევითი" }, "randomWord": { "message": "Random word" @@ -2835,7 +2859,7 @@ "message": "Password type" }, "service": { - "message": "Service" + "message": "სერვისი" }, "forwardedEmail": { "message": "Forwarded email alias" @@ -2946,14 +2970,14 @@ } }, "hostname": { - "message": "Hostname", + "message": "ჰოსტის სახელი", "description": "Part of a URL." }, "apiAccessToken": { "message": "API Access Token" }, "apiKey": { - "message": "API Key" + "message": "API-ის გასაღები" }, "ssoKeyConnectorError": { "message": "Key connector error: make sure key connector is available and working correctly." @@ -2980,19 +3004,19 @@ "message": "Settings have been edited" }, "environmentEditedClick": { - "message": "Click here" + "message": "დააწკაპუნეთ აქ" }, "environmentEditedReset": { "message": "to reset to pre-configured settings" }, "serverVersion": { - "message": "Server version" + "message": "სერვერის ვერსია" }, "selfHostedServer": { "message": "self-hosted" }, "thirdParty": { - "message": "Third-party" + "message": "მესამე-პირი" }, "thirdPartyServerMessage": { "message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.", @@ -3100,7 +3124,7 @@ "message": "Select an item from this screen, or explore other options in settings." }, "gotIt": { - "message": "Got it" + "message": "გავიგე" }, "autofillSettings": { "message": "Autofill settings" @@ -3112,7 +3136,7 @@ "message": "Change shortcut" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "მალსახმობების მართვა" }, "autofillShortcut": { "message": "Autofill keyboard shortcut" @@ -3169,7 +3193,7 @@ "message": "Creating account on" }, "checkYourEmail": { - "message": "Check your email" + "message": "შეამოწმეთ თქვენი ელფოსტა" }, "followTheLinkInTheEmailSentTo": { "message": "Follow the link in the email sent to" @@ -3181,7 +3205,7 @@ "message": "No email?" }, "goBack": { - "message": "Go back" + "message": "უკან დაბრუნება" }, "toEditYourEmailAddress": { "message": "to edit your email address." @@ -3194,10 +3218,10 @@ "message": "Access denied. You do not have permission to view this page." }, "general": { - "message": "General" + "message": "ზოგადი" }, "display": { - "message": "Display" + "message": "ჩვენება" }, "accountSuccessfullyCreated": { "message": "Account successfully created!" @@ -3235,10 +3259,10 @@ "message": "Input is required." }, "required": { - "message": "required" + "message": "აუცილებელია" }, "search": { - "message": "Search" + "message": "ძებნა" }, "inputMinLength": { "message": "Input must be at least $COUNT$ characters long.", @@ -3317,7 +3341,7 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- არჩევა --" }, "multiSelectPlaceholder": { "message": "-- Type to filter --" @@ -3326,10 +3350,10 @@ "message": "Retrieving options..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "ჩანაწერები ნაპოვნი არაა" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "ყველას გასუფთავება" }, "plusNMore": { "message": "+ $QUANTITY$ more", @@ -3341,7 +3365,7 @@ } }, "submenu": { - "message": "Submenu" + "message": "ქვემენიუ" }, "toggleCollapse": { "message": "Toggle collapse", @@ -3364,7 +3388,7 @@ "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importing...", + "message": "შემოტანა...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { @@ -3398,7 +3422,7 @@ "message": "Toggle side navigation" }, "skipToContent": { - "message": "Skip to content" + "message": "შემცველობაზე გადახტომა" }, "bitwardenOverlayButton": { "message": "Bitwarden autofill menu button", @@ -3421,7 +3445,7 @@ "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "ანგარიშის განბლოკვა", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { @@ -3441,7 +3465,7 @@ "description": "Text to show in overlay if there are no matching items" }, "newItem": { - "message": "New item", + "message": "ახალი ჩანაწერი", "description": "Button text to display in overlay when there are no matching items" }, "addNewVaultItem": { @@ -3477,17 +3501,17 @@ "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { - "message": "Turn on" + "message": "ჩართვა" }, "ignore": { - "message": "Ignore" + "message": "იგნორი" }, "importData": { - "message": "Import data", + "message": "მონაცემების შემოტანა", "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "შემოტანის შეცდომა" }, "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." @@ -3496,7 +3520,7 @@ "message": "Resolve the errors below and try again." }, "description": { - "message": "Description" + "message": "აღწერა" }, "importSuccess": { "message": "Data successfully imported" @@ -3511,7 +3535,7 @@ } }, "tryAgain": { - "message": "Try again" + "message": "კიდევ სცადეთ" }, "verificationRequiredForActionSetPinToContinue": { "message": "Verification required for this action. Set a PIN to continue." @@ -3547,7 +3571,7 @@ "message": "Resend code" }, "total": { - "message": "Total" + "message": "სულ" }, "importWarning": { "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", @@ -3589,16 +3613,16 @@ "message": "Invalid file password, please use the password you entered when you created the export file." }, "destination": { - "message": "Destination" + "message": "სამიზნე" }, "learnAboutImportOptions": { "message": "Learn about your import options" }, "selectImportFolder": { - "message": "Select a folder" + "message": "აირჩიეთ საქაღალდე" }, "selectImportCollection": { - "message": "Select a collection" + "message": "აირჩიეთ კოლექცია" }, "importTargetHint": { "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", @@ -3620,10 +3644,10 @@ "message": "Select the import file" }, "chooseFile": { - "message": "Choose File" + "message": "ფაილის არჩევა" }, "noFileChosen": { - "message": "No file chosen" + "message": "ფაილი არჩეული არაა" }, "orCopyPasteFileContents": { "message": "or copy/paste the import file contents" @@ -3651,10 +3675,10 @@ "message": "Vault data exported" }, "typePasskey": { - "message": "Passkey" + "message": "საკვანძო გასაღები" }, "accessing": { - "message": "Accessing" + "message": "წვდომა" }, "passkeyNotCopied": { "message": "Passkey will not be copied" @@ -3684,7 +3708,7 @@ "message": "Search or save passkey as new login" }, "confirm": { - "message": "Confirm" + "message": "დადასტურება" }, "savePasskey": { "message": "Save passkey" @@ -3720,16 +3744,16 @@ "message": "No LastPass data found" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "არასწორი მომხმარებელი ან პაროლი" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "არასწორი პაროლი" }, "incorrectCode": { "message": "Incorrect code" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN კოდი არასწორია" }, "multifactorAuthenticationFailed": { "message": "Multifactor authentication failed" @@ -3753,7 +3777,7 @@ "message": "Approve the login request in your authentication app or enter a one-time passcode." }, "passcode": { - "message": "Passcode" + "message": "საკვანძო კოდი" }, "lastPassMasterPassword": { "message": "LastPass master password" @@ -3775,19 +3799,19 @@ "message": "Import directly from LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "CVS-დან შემოტანა" }, "lastPassTryAgainCheckEmail": { "message": "Try again or look for an email from LastPass to verify it's you." }, "collection": { - "message": "Collection" + "message": "კოლექცია" }, "lastPassYubikeyDesc": { "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." }, "switchAccount": { - "message": "Switch account" + "message": "ანგარიშის გადართვა" }, "switchAccounts": { "message": "Switch accounts" @@ -3805,16 +3829,16 @@ "message": "Account limit reached. Log out of an account to add another." }, "active": { - "message": "active" + "message": "აქტიური" }, "locked": { - "message": "locked" + "message": "დაბლოკილია" }, "unlocked": { "message": "unlocked" }, "server": { - "message": "server" + "message": "სერვერი" }, "hostedAt": { "message": "hosted at" @@ -3823,7 +3847,7 @@ "message": "Use your device or hardware key" }, "justOnce": { - "message": "Just once" + "message": "მხოლოდ ერთხელ" }, "alwaysForThisSite": { "message": "Always for this site" @@ -3886,7 +3910,7 @@ "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "ნაგულისხმებად გამოყენება", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { @@ -3910,7 +3934,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "წარმატება" }, "removePasskey": { "message": "Remove passkey" @@ -4006,19 +4030,19 @@ "message": "Copy phone" }, "copyAddress": { - "message": "Copy address" + "message": "მისამართის დაკოპირება" }, "adminConsole": { "message": "Admin Console" }, "accountSecurity": { - "message": "Account security" + "message": "ანგარიშის უსაფრთხოება" }, "notifications": { - "message": "Notifications" + "message": "გაფრთხილებები" }, "appearance": { - "message": "Appearance" + "message": "გარეგნობა" }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." @@ -4047,7 +4071,7 @@ } }, "new": { - "message": "New" + "message": "ახალი" }, "removeItem": { "message": "Remove $NAME$", @@ -4066,7 +4090,7 @@ "message": "Item details" }, "itemName": { - "message": "Item name" + "message": "ჩანაწერის სახელი" }, "cannotRemoveViewOnlyCollections": { "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", @@ -4081,10 +4105,10 @@ "message": "Organization is deactivated" }, "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": { @@ -4097,22 +4121,22 @@ "message": "Item history" }, "lastEdited": { - "message": "Last edited" + "message": "ბოლოს ჩასწორებული" }, "ownerYou": { "message": "Owner: You" }, "linked": { - "message": "Linked" + "message": "მიბმული" }, "copySuccessful": { "message": "Copy Successful" }, "upload": { - "message": "Upload" + "message": "ატვირთვა" }, "addAttachment": { - "message": "Add attachment" + "message": "მიმაგრებული ფაილის დამატება" }, "maxFileSizeSansPunctuation": { "message": "Maximum file size is 500 MB" @@ -4139,22 +4163,22 @@ "message": "Are you sure you want to permanently delete this attachment?" }, "premium": { - "message": "Premium" + "message": "პრემიუმი" }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, "filters": { - "message": "Filters" + "message": "ფილტრები" }, "personalDetails": { "message": "Personal details" }, "identification": { - "message": "Identification" + "message": "იდენტიფიკაცია" }, "contactInfo": { - "message": "Contact info" + "message": "საკონტაქტო ინფორმაცია" }, "downloadAttachment": { "message": "Download - $ITEMNAME$", @@ -4238,7 +4262,7 @@ "message": "If you've renewed it, update the card's information" }, "cardDetails": { - "message": "Card details" + "message": "ბარათის დეტალები" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -4250,26 +4274,26 @@ } }, "enableAnimations": { - "message": "Enable animations" + "message": "ანიმაციების ჩართვა" }, "showAnimations": { "message": "Show animations" }, "addAccount": { - "message": "Add account" + "message": "ანგარიშის დამატება" }, "loading": { - "message": "Loading" + "message": "იტვირთება" }, "data": { - "message": "Data" + "message": "მონაცემები" }, "passkeys": { "message": "Passkeys", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "პაროლები", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { @@ -4277,7 +4301,7 @@ "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." @@ -4298,13 +4322,13 @@ } }, "addField": { - "message": "Add field" + "message": "ველის დამატება" }, "add": { - "message": "Add" + "message": "დამატება" }, "fieldType": { - "message": "Field type" + "message": "ველის ტიპი" }, "fieldLabel": { "message": "Field label" @@ -4489,13 +4513,13 @@ "message": "Show number of login autofill suggestions on extension icon" }, "systemDefault": { - "message": "System default" + "message": "სისტემურად ნაგულისხმევი" }, "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, "retry": { - "message": "Retry" + "message": "თავიდან ცდა" }, "vaultCustomTimeoutMinimum": { "message": "Minimum custom timeout is 1 minute." @@ -4525,7 +4549,7 @@ "message": "Items that have been in trash more than 30 days will automatically be deleted" }, "restore": { - "message": "Restore" + "message": "აღდგენა" }, "deleteForever": { "message": "Delete forever" @@ -4534,6 +4558,6 @@ "message": "You don't have permission to edit this item" }, "authenticating": { - "message": "Authenticating" + "message": "ავთენტიკაცია" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index e52f78583d4..290663f4347 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 4751a2e3a5b..acf6340df91 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 7d4e8a7bc68..7a03c87713a 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "인증 필요", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 3249db20116..fe3d17678ee 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Jūsų organizacija reikalauja Jus nustatyti pagrindinį slaptažodį.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Reikalingas patikrinimas", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index dd60641046d..72622132b4b 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Pievienoties apvienībai" }, + "joinOrganizationName": { + "message": "Pievienoties $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Pabeigt pievienošanos šai apvienībai ar galvenās paroles iestatīšanu." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Rādīt automātiskās aizpildes ieteikumuis veidlapu laukos" }, + "showInlineMenuIdentitiesLabel": { + "message": "Attēlot identitātes kā ieteikumus" + }, + "showInlineMenuCardsLabel": { + "message": "Attēlot kartes kā ieteikumus" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Attēlot ieteikumus, kad tiek atlasīta ikona" }, @@ -2647,6 +2662,15 @@ "message": "Apvienība pieprasa iestatīt galveno paroli.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "no $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Nepieciešams apliecinājums", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 9e285ceeb43..603324f548f 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 009a1ffc2fa..ae0112d1dd0 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index e52f78583d4..290663f4347 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 096b764f1fc..3f634e272a6 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verifisering kreves", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index e52f78583d4..290663f4347 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index dcc1ac56e9b..878518531f8 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Lid van organisatie worden" }, + "joinOrganizationName": { + "message": "Aansluiten bij $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Voltooi je lidmaatschap aan deze organisatie door een hoofdwachtwoord in te stellen." }, @@ -1046,7 +1055,7 @@ "message": "Bestandsindeling" }, "fileEncryptedExportWarningDesc": { - "message": "We beveiligen deze bestandsexport met een wachtwoord beveiligd, je hebt het bestandswachtwoord nodig om het te decoderen." + "message": "We beveiligen deze bestandsexport met een wachtwoord, je hebt het bestandswachtwoord nodig om het bestand te decoderen." }, "filePassword": { "message": "Bestandswachtwoord" @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Suggesties voor automatisch invullen op formuliervelden weergeven" }, + "showInlineMenuIdentitiesLabel": { + "message": "Identiteiten als suggesties weergeven" + }, + "showInlineMenuCardsLabel": { + "message": "Kaarten als suggesties weergeven" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Suggesties weergeven wanneer pictogram is geselecteerd" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "van $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verificatie vereist", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index e52f78583d4..290663f4347 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index e52f78583d4..290663f4347 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index d05ccc7ae0b..7340d09f8df 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Dołącz do organizacji" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Zakończ dołączanie do tej organizacji przez ustawienie hasła głównego." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Pokaż sugestie autouzupełniania na polach formularza" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Wyświetlaj sugestie kiedy ikona jest zaznaczona" }, @@ -2647,6 +2662,15 @@ "message": "Twoja organizacja wymaga ustawienia hasła głównego.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Wymagana weryfikacja", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 55654fd4355..11a277c41b6 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Juntar-se à organização" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Termine de juntar-se nessa organização definindo uma senha mestra." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Mostrar sugestões de preenchimento automático nos campos de formulários" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Exibir sugestões quando o ícone for selecionado" }, @@ -2647,6 +2662,15 @@ "message": "Sua organização requer que você defina uma senha mestra.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verificação necessária", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 738363679ed..2bdb2b5a935 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Aderir à organização" }, + "joinOrganizationName": { + "message": "Aderir a $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Conclua a adesão a esta organização ao definir uma palavra-passe mestra." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Mostrar sugestões de preenchimento automático nos campos do formulário" }, + "showInlineMenuIdentitiesLabel": { + "message": "Apresentar as identidades como sugestões" + }, + "showInlineMenuCardsLabel": { + "message": "Apresentar os cartões como sugestões" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Apresentar sugestões quando o ícone é selecionado" }, @@ -2647,6 +2662,15 @@ "message": "A sua organização exige a definição de uma palavra-passe mestra.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "de $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verificação necessária", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index cbee5127a53..7b506d5a3e9 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Alăturați-vă organizației" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finalizați aderarea la această organizație prin setarea unei parole principale." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index ffdb59465dd..56a58cfb9f1 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Присоединиться к организации" }, + "joinOrganizationName": { + "message": "Присоединиться к $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Завершите присоединение к этой организации, установив мастер-пароль." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Показывать предположения автозаполнения в полях формы" }, + "showInlineMenuIdentitiesLabel": { + "message": "Показывать Личности как предложения" + }, + "showInlineMenuCardsLabel": { + "message": "Показывать Карты как предложения" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Показывать подсказки при выборе значка" }, @@ -2647,6 +2662,15 @@ "message": "Необходимо установить мастер-пароль для организации.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "из $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Требуется верификация", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 030deefac2f..1636f483ed6 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 87ca21a89e1..1bf25cac9f8 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Pripojte sa k organizácii" }, + "joinOrganizationName": { + "message": "Pripojiť sa k $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Dokončite pripojenie k tejto organizácii nastavením hlavného hesla." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Zobraziť návrhy automatického vypĺňania v poliach formulára" }, + "showInlineMenuIdentitiesLabel": { + "message": "Zobrazovať identity ako návrhy" + }, + "showInlineMenuCardsLabel": { + "message": "Zobrazovať karty ako návrhy" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Zobraziť návrhy, keď je vybratá ikona" }, @@ -2647,6 +2662,15 @@ "message": "Vaša organizácia vyžaduje, aby ste nastavili hlavné heslo.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "z $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Vyžaduje sa overenie", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 50896bcd21f..9fab3ca10df 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index f9c366ce5fc..484a44af4d1 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Придружи Организацију" }, + "joinOrganizationName": { + "message": "Придружити се $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Завршите придруживање овој организацији постављањем главне лозинке." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Прикажи предлоге за ауто-попуњавање у пољима обрасца" }, + "showInlineMenuIdentitiesLabel": { + "message": "Приказати идентитете као предлоге" + }, + "showInlineMenuCardsLabel": { + "message": "Приказати картице као предлоге" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Приказати предлоге када је изабрана икона" }, @@ -2647,6 +2662,15 @@ "message": "Ваша организација захтева да поставите главну лозинку.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "од $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Потребдна верификација", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 2852e3ad1f3..bbc33e9456e 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Din organisation kräver att du anger ett huvudlösenord.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verifiering krävs", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index e52f78583d4..290663f4347 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index f4cba694d49..66903ea6e6e 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Join organization" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Finish joining this organization by setting a master password." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "Your organization requires you to set a master password.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index b26f3fe64aa..a7ea5dfd146 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Kuruluşa katıl" }, + "joinOrganizationName": { + "message": "$ORGANIZATIONNAME$ kuruluşuna katıl", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Kuruluşa katılmayı tamamlamak için ana parolanızı belirleyin." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Form alanlarında otomatik doldurma önerilerini göster" }, + "showInlineMenuIdentitiesLabel": { + "message": "Kimlikleri öneri olarak göster" + }, + "showInlineMenuCardsLabel": { + "message": "Kartları öneri olarak göster" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Simge seçildiğinde önerileri göster" }, @@ -2647,6 +2662,15 @@ "message": "Kuruluşunuz bir ana parola belirlemenizi gerektiriyor.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "/ $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Doğrulama gerekli", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 702b58a62c9..d5b6d5ade70 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Приєднатися до організації" }, + "joinOrganizationName": { + "message": "Приєднатися до $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Завершіть приєднання до цієї організації, встановивши головний пароль." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Пропозиції автозаповнення на полях форм" }, + "showInlineMenuIdentitiesLabel": { + "message": "Показувати посвідчення як пропозиції" + }, + "showInlineMenuCardsLabel": { + "message": "Показувати картки як пропозиції" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Показувати пропозиції, якщо вибрано піктограму" }, @@ -2499,11 +2514,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "Це відправлення буде доступним будь-кому за посиланням протягом 1 години.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "Це відправлення буде доступним будь-кому за посиланням протягом $HOURS$ годин.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2513,11 +2528,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "Це відправлення буде доступним будь-кому за посиланням протягом 1 дня.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Це відправлення буде доступним будь-кому за посиланням протягом $DAYS$ днів.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2647,6 +2662,15 @@ "message": "Ваша організація вимагає, щоб ви встановили головний пароль.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "з $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Потрібне підтвердження", "description": "Default title for the user verification dialog." @@ -3681,7 +3705,7 @@ "message": "Немає відповідних записів для цього сайту" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Шукати або зберегти ключ доступу як новий запис" }, "confirm": { "message": "Підтвердити" diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 42fc36a8f2b..a378a56aa9f 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "Tham gia tổ chức" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "Hoàn tất gia nhập tổ chức này bằng cách đặt một mật khẩu chính." }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Hiển thị các gợi ý tự động điền trên các trường biểu mẫu" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Hiện gợi ý khi nhấp vào biểu tượng" }, @@ -2647,6 +2662,15 @@ "message": "Tổ chức của bạn yêu cầu bạn đặt mật khẩu chính.", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "Yêu cầu xác minh", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 7cbc946cd0e..ad89f8efc79 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "加入组织" }, + "joinOrganizationName": { + "message": "加入 $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "通过设置主密码完成加入此组织。" }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "在表单字段中显示自动填充建议" }, + "showInlineMenuIdentitiesLabel": { + "message": "将身份显示为建议" + }, + "showInlineMenuCardsLabel": { + "message": "将支付卡显示为建议" + }, "showInlineMenuOnIconSelectionLabel": { "message": "选择图标时显示建议" }, @@ -2499,11 +2514,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "在接下来的 1 小时内,任何人都可以通过链接访问此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "在接下来的 $HOURS$ 小时内,任何人都可以通过链接访问此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2513,11 +2528,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "在接下来的 1 天内,任何人都可以通过链接访问此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "在接下来的 $DAYS$ 天内,任何人都可以通过链接访问此 Send。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2647,6 +2662,15 @@ "message": "您的组织要求您设置主密码。", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "$TOTAL$ 不足", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "需要验证", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 5d961e1fe10..496d7b8159a 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -71,6 +71,15 @@ "joinOrganization": { "message": "加入組織" }, + "joinOrganizationName": { + "message": "Join $ORGANIZATIONNAME$", + "placeholders": { + "organizationName": { + "content": "$1", + "example": "My Org Name" + } + } + }, "finishJoiningThisOrganizationBySettingAMasterPassword": { "message": "設定主密碼以完成加入這個組織" }, @@ -1408,6 +1417,12 @@ "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, + "showInlineMenuIdentitiesLabel": { + "message": "Display identities as suggestions" + }, + "showInlineMenuCardsLabel": { + "message": "Display cards as suggestions" + }, "showInlineMenuOnIconSelectionLabel": { "message": "Display suggestions when icon is selected" }, @@ -2647,6 +2662,15 @@ "message": "您的組織要求您設定主密碼。", "description": "Used as a card title description on the set password page to explain why the user is there" }, + "cardMetrics": { + "message": "out of $TOTAL$", + "placeholders": { + "total": { + "content": "$1", + "example": "5" + } + } + }, "verificationRequired": { "message": "需要驗證", "description": "Default title for the user verification dialog." diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts index 9d7644878d0..db85b28fa64 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts @@ -131,31 +131,35 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { return; } - if (data.pageTitle) { - this.pageTitle = this.handleStringOrTranslation(data.pageTitle); + // Null emissions are used to reset the page data as all fields are optional. + + if (data.pageTitle !== undefined) { + this.pageTitle = + data.pageTitle !== null ? this.handleStringOrTranslation(data.pageTitle) : null; } - if (data.pageSubtitle) { - this.pageSubtitle = this.handleStringOrTranslation(data.pageSubtitle); + if (data.pageSubtitle !== undefined) { + this.pageSubtitle = + data.pageSubtitle !== null ? this.handleStringOrTranslation(data.pageSubtitle) : null; } - if (data.pageIcon) { - this.pageIcon = data.pageIcon; + if (data.pageIcon !== undefined) { + this.pageIcon = data.pageIcon !== null ? data.pageIcon : null; } - if (data.showReadonlyHostname != null) { + if (data.showReadonlyHostname !== undefined) { this.showReadonlyHostname = data.showReadonlyHostname; } - if (data.showAcctSwitcher != null) { + if (data.showAcctSwitcher !== undefined) { this.showAcctSwitcher = data.showAcctSwitcher; } - if (data.showBackButton != null) { + if (data.showBackButton !== undefined) { this.showBackButton = data.showBackButton; } - if (data.showLogo != null) { + if (data.showLogo !== undefined) { this.showLogo = data.showLogo; } } diff --git a/apps/browser/src/auth/popup/hint.component.ts b/apps/browser/src/auth/popup/hint.component.ts index bc1f68f4c43..e97236fe6a8 100644 --- a/apps/browser/src/auth/popup/hint.component.ts +++ b/apps/browser/src/auth/popup/hint.component.ts @@ -34,7 +34,7 @@ export class HintComponent extends BaseHintComponent { toastService, ); - super.onSuccessfulSubmit = async () => { + this.onSuccessfulSubmit = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.router.navigate([this.successRoute]); diff --git a/apps/browser/src/auth/popup/lock.component.ts b/apps/browser/src/auth/popup/lock.component.ts index 96bda7012d1..75fcfc58f6a 100644 --- a/apps/browser/src/auth/popup/lock.component.ts +++ b/apps/browser/src/auth/popup/lock.component.ts @@ -105,7 +105,7 @@ export class LockComponent extends BaseLockComponent implements OnInit { this.successRoute = "/tabs/current"; this.isInitialLockScreen = (window as any).previousPopupUrl == null; - super.onSuccessfulSubmit = async () => { + this.onSuccessfulSubmit = async () => { const previousUrl = this.routerService.getPreviousUrl(); if (previousUrl) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. diff --git a/apps/browser/src/auth/popup/login-via-auth-request.component.ts b/apps/browser/src/auth/popup/login-via-auth-request.component.ts index 53f29badee6..33ec2acf387 100644 --- a/apps/browser/src/auth/popup/login-via-auth-request.component.ts +++ b/apps/browser/src/auth/popup/login-via-auth-request.component.ts @@ -74,7 +74,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { loginStrategyService, toastService, ); - super.onSuccessfulLogin = async () => { + this.onSuccessfulLogin = async () => { await syncService.fullSync(true); }; } diff --git a/apps/browser/src/auth/popup/login.component.ts b/apps/browser/src/auth/popup/login.component.ts index ea72fb61f5f..fd4d9bc547a 100644 --- a/apps/browser/src/auth/popup/login.component.ts +++ b/apps/browser/src/auth/popup/login.component.ts @@ -78,10 +78,10 @@ export class LoginComponent extends BaseLoginComponent implements OnInit { registerRouteService, toastService, ); - super.onSuccessfulLogin = async () => { + this.onSuccessfulLogin = async () => { await syncService.fullSync(true); }; - super.successRoute = "/tabs/vault"; + this.successRoute = "/tabs/vault"; this.showPasswordless = flagEnabled("showPasswordless"); } diff --git a/apps/browser/src/auth/popup/sso.component.ts b/apps/browser/src/auth/popup/sso.component.ts index 42222c42b97..988563c2fe6 100644 --- a/apps/browser/src/auth/popup/sso.component.ts +++ b/apps/browser/src/auth/popup/sso.component.ts @@ -79,7 +79,7 @@ export class SsoComponent extends BaseSsoComponent { }); this.clientId = "browser"; - super.onSuccessfulLogin = async () => { + this.onSuccessfulLogin = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises syncService.fullSync(true); @@ -92,13 +92,13 @@ export class SsoComponent extends BaseSsoComponent { this.win.close(); }; - super.onSuccessfulLoginTde = async () => { + this.onSuccessfulLoginTde = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises syncService.fullSync(true); }; - super.onSuccessfulLoginTdeNavigate = async () => { + this.onSuccessfulLoginTdeNavigate = async () => { this.win.close(); }; } 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 27c95321100..9e755746e6f 100644 --- a/apps/browser/src/auth/popup/two-factor-auth.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth.component.ts @@ -118,7 +118,7 @@ export class TwoFactorAuthComponent win, toastService, ); - super.onSuccessfulLoginTdeNavigate = async () => { + this.onSuccessfulLoginTdeNavigate = async () => { this.win.close(); }; this.onSuccessfulLoginNavigate = this.goAfterLogIn; @@ -131,7 +131,7 @@ export class TwoFactorAuthComponent // WebAuthn fallback response this.selectedProviderType = TwoFactorProviderType.WebAuthn; this.token = this.route.snapshot.paramMap.get("webAuthnResponse"); - super.onSuccessfulLogin = async () => { + this.onSuccessfulLogin = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.syncService.fullSync(true); diff --git a/apps/browser/src/auth/popup/two-factor.component.ts b/apps/browser/src/auth/popup/two-factor.component.ts index e9167a5087a..27c4604be91 100644 --- a/apps/browser/src/auth/popup/two-factor.component.ts +++ b/apps/browser/src/auth/popup/two-factor.component.ts @@ -87,23 +87,23 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit accountService, toastService, ); - super.onSuccessfulLogin = async () => { + this.onSuccessfulLogin = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises syncService.fullSync(true); }; - super.onSuccessfulLoginTde = async () => { + this.onSuccessfulLoginTde = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises syncService.fullSync(true); }; - super.onSuccessfulLoginTdeNavigate = async () => { + this.onSuccessfulLoginTdeNavigate = async () => { this.win.close(); }; - super.successRoute = "/tabs/vault"; + this.successRoute = "/tabs/vault"; // FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe this.webAuthnNewTab = true; } @@ -113,7 +113,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit // WebAuthn fallback response this.selectedProviderType = TwoFactorProviderType.WebAuthn; this.token = this.route.snapshot.paramMap.get("webAuthnResponse"); - super.onSuccessfulLogin = async () => { + this.onSuccessfulLogin = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.syncService.fullSync(true); @@ -155,7 +155,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.queryParams.pipe(first()).subscribe(async (qParams) => { if (qParams.sso === "true") { - super.onSuccessfulLogin = async () => { + this.onSuccessfulLogin = async () => { // This is not awaited so we don't pause the application while the sync is happening. // This call is executed by the service that lives in the background script so it will continue // the sync even if this tab closes. diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index e91a58a84cf..abe7d097016 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -188,6 +188,8 @@ export type OverlayBackgroundExtensionMessageHandlers = { updateIsFieldCurrentlyFilling: ({ message }: BackgroundMessageParam) => void; checkIsFieldCurrentlyFilling: () => boolean; getAutofillInlineMenuVisibility: () => void; + getInlineMenuCardsVisibility: () => void; + getInlineMenuIdentitiesVisibility: () => void; openAutofillInlineMenu: () => void; closeAutofillInlineMenu: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; checkAutofillInlineMenuFocused: ({ sender }: BackgroundSenderParam) => void; diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index b45a4a25485..49788d67404 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -132,6 +132,8 @@ export class OverlayBackground implements OverlayBackgroundInterface { updateIsFieldCurrentlyFilling: ({ message }) => this.updateIsFieldCurrentlyFilling(message), checkIsFieldCurrentlyFilling: () => this.checkIsFieldCurrentlyFilling(), getAutofillInlineMenuVisibility: () => this.getInlineMenuVisibility(), + getInlineMenuCardsVisibility: () => this.getInlineMenuCardsVisibility(), + getInlineMenuIdentitiesVisibility: () => this.getInlineMenuIdentitiesVisibility(), openAutofillInlineMenu: () => this.openInlineMenu(false), closeAutofillInlineMenu: ({ message, sender }) => this.closeInlineMenu(sender, message), checkAutofillInlineMenuFocused: ({ sender }) => this.checkInlineMenuFocused(sender), @@ -365,7 +367,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } } - if (!this.cardAndIdentityCiphers.size) { + if (!this.cardAndIdentityCiphers?.size) { this.cardAndIdentityCiphers = null; } @@ -1483,6 +1485,20 @@ export class OverlayBackground implements OverlayBackgroundInterface { return await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$); } + /** + * Gets the inline menu's visibility setting for Cards from the settings service. + */ + private async getInlineMenuCardsVisibility(): Promise { + return await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$); + } + + /** + * Gets the inline menu's visibility setting for Identities from the settings service. + */ + private async getInlineMenuIdentitiesVisibility(): Promise { + return await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$); + } + /** * Gets the user's authentication status from the auth service. */ diff --git a/apps/browser/src/autofill/content/abstractions/autofill-init.ts b/apps/browser/src/autofill/content/abstractions/autofill-init.ts index ba815a0f29a..529607949db 100644 --- a/apps/browser/src/autofill/content/abstractions/autofill-init.ts +++ b/apps/browser/src/autofill/content/abstractions/autofill-init.ts @@ -1,4 +1,5 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AutofillOverlayElementType } from "../../enums/autofill-overlay.enum"; @@ -23,7 +24,7 @@ export type AutofillExtensionMessage = { data?: { direction?: "previous" | "next" | "current"; forceCloseInlineMenu?: boolean; - inlineMenuVisibility?: number; + newSettingValue?: InlineMenuVisibilitySetting; }; }; diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts index 8d5e08fc08e..c9d86cffc5c 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts @@ -14,7 +14,7 @@ describe("AutofillInlineMenuContentService", () => { let autofillInlineMenuContentService: AutofillInlineMenuContentService; let autofillInit: AutofillInit; let sendExtensionMessageSpy: jest.SpyInstance; - let observeBodyMutationsSpy: jest.SpyInstance; + let observeContainerMutationsSpy: jest.SpyInstance; const waitForIdleCallback = () => new Promise((resolve) => globalThis.requestIdleCallback(resolve)); @@ -25,8 +25,8 @@ describe("AutofillInlineMenuContentService", () => { autofillInlineMenuContentService = new AutofillInlineMenuContentService(); autofillInit = new AutofillInit(domQueryService, null, autofillInlineMenuContentService); autofillInit.init(); - observeBodyMutationsSpy = jest.spyOn( - autofillInlineMenuContentService["bodyElementMutationObserver"] as any, + observeContainerMutationsSpy = jest.spyOn( + autofillInlineMenuContentService["containerElementMutationObserver"] as any, "observe", ); sendExtensionMessageSpy = jest.spyOn( @@ -51,7 +51,7 @@ describe("AutofillInlineMenuContentService", () => { describe("extension message handlers", () => { describe("closeAutofillInlineMenu message handler", () => { beforeEach(() => { - observeBodyMutationsSpy.mockImplementation(); + observeContainerMutationsSpy.mockImplementation(); }); it("closes the inline menu button", async () => { @@ -87,9 +87,9 @@ describe("AutofillInlineMenuContentService", () => { }); it("closes both inline menu elements and removes the body element mutation observer", async () => { - const unobserveBodyElementSpy = jest.spyOn( + const unobserveContainerElementSpy = jest.spyOn( autofillInlineMenuContentService as any, - "unobserveBodyElement", + "unobserveContainerElement", ); sendMockExtensionMessage({ command: "appendAutofillInlineMenuToDom", @@ -104,7 +104,7 @@ describe("AutofillInlineMenuContentService", () => { command: "closeAutofillInlineMenu", }); - expect(unobserveBodyElementSpy).toHaveBeenCalled(); + expect(unobserveContainerElementSpy).toHaveBeenCalled(); expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayElementClosed", { overlayElement: AutofillOverlayElement.Button, }); @@ -127,7 +127,7 @@ describe("AutofillInlineMenuContentService", () => { .spyOn(autofillInlineMenuContentService as any, "isInlineMenuListVisible") .mockResolvedValue(true); jest.spyOn(globalThis.document.body, "appendChild"); - observeBodyMutationsSpy.mockImplementation(); + observeContainerMutationsSpy.mockImplementation(); }); describe("creating the inline menu button", () => { @@ -279,7 +279,8 @@ describe("AutofillInlineMenuContentService", () => { }); }); - describe("handleBodyElementMutationObserverUpdate", () => { + describe("handleContainerElementMutationObserverUpdate", () => { + let mockMutationRecord: MockProxy; let buttonElement: HTMLElement; let listElement: HTMLElement; let isInlineMenuListVisibleSpy: jest.SpyInstance; @@ -289,6 +290,7 @@ describe("AutofillInlineMenuContentService", () => {
`; + mockMutationRecord = mock({ target: globalThis.document.body } as any); buttonElement = document.querySelector(".overlay-button") as HTMLElement; listElement = document.querySelector(".overlay-list") as HTMLElement; autofillInlineMenuContentService["buttonElement"] = buttonElement; @@ -309,7 +311,9 @@ describe("AutofillInlineMenuContentService", () => { autofillInlineMenuContentService["buttonElement"] = undefined; autofillInlineMenuContentService["listElement"] = undefined; - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); @@ -323,7 +327,9 @@ describe("AutofillInlineMenuContentService", () => { ) .mockReturnValue(true); - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); @@ -332,14 +338,18 @@ describe("AutofillInlineMenuContentService", () => { it("skips re-arranging the DOM elements if the last child of the body is non-existent", async () => { document.body.innerHTML = ""; - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); }); it("skips re-arranging the DOM elements if the last child of the body is the overlay list and the second to last child of the body is the overlay button", async () => { - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); @@ -349,7 +359,9 @@ describe("AutofillInlineMenuContentService", () => { listElement.remove(); isInlineMenuListVisibleSpy.mockResolvedValue(false); - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); @@ -359,7 +371,9 @@ describe("AutofillInlineMenuContentService", () => { const injectedElement = document.createElement("div"); document.body.insertBefore(injectedElement, listElement); - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith( @@ -371,7 +385,9 @@ describe("AutofillInlineMenuContentService", () => { it("positions the overlay button before the overlay list if the elements have inserted in incorrect order", async () => { document.body.appendChild(buttonElement); - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith( @@ -384,7 +400,9 @@ describe("AutofillInlineMenuContentService", () => { const injectedElement = document.createElement("div"); document.body.appendChild(injectedElement); - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith( @@ -409,7 +427,9 @@ describe("AutofillInlineMenuContentService", () => { 1000, ); - await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + autofillInlineMenuContentService["handleContainerElementMutationObserverUpdate"]([ + mockMutationRecord, + ]); await waitForIdleCallback(); expect(persistentLastChild.style.getPropertyValue("z-index")).toBe("2147483646"); diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts index 02d3ae052cc..110c1be7db8 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts @@ -30,7 +30,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte private buttonElement: HTMLElement; private listElement: HTMLElement; private inlineMenuElementsMutationObserver: MutationObserver; - private bodyElementMutationObserver: MutationObserver; + private containerElementMutationObserver: MutationObserver; private mutationObserverIterations = 0; private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout; private handlePersistentLastChildOverrideTimeout: number | NodeJS.Timeout; @@ -102,7 +102,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte return; } - this.unobserveBodyElement(); + this.unobserveContainerElement(); this.closeInlineMenuButton(); this.closeInlineMenuList(); }; @@ -153,7 +153,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte } if (!(await this.isInlineMenuButtonVisible())) { - this.appendInlineMenuElementToBody(this.buttonElement); + this.appendInlineMenuElementToDom(this.buttonElement); this.updateInlineMenuElementIsVisibleStatus(AutofillOverlayElement.Button, true); } } @@ -168,7 +168,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte } if (!(await this.isInlineMenuListVisible())) { - this.appendInlineMenuElementToBody(this.listElement); + this.appendInlineMenuElementToDom(this.listElement); this.updateInlineMenuElementIsVisibleStatus(AutofillOverlayElement.List, true); } } @@ -196,8 +196,15 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * * @param element - The inline menu element to append to the body element. */ - private appendInlineMenuElementToBody(element: HTMLElement) { - this.observeBodyElement(); + private appendInlineMenuElementToDom(element: HTMLElement) { + const parentDialogElement = globalThis.document.activeElement?.closest("dialog"); + if (parentDialogElement && parentDialogElement.open && parentDialogElement.matches(":modal")) { + this.observeContainerElement(parentDialogElement); + parentDialogElement.appendChild(element); + return; + } + + this.observeContainerElement(globalThis.document.body); globalThis.document.body.appendChild(element); } @@ -276,8 +283,8 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte this.handleInlineMenuElementMutationObserverUpdate, ); - this.bodyElementMutationObserver = new MutationObserver( - this.handleBodyElementMutationObserverUpdate, + this.containerElementMutationObserver = new MutationObserver( + this.handleContainerElementMutationObserverUpdate, ); }; @@ -306,19 +313,17 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte } /** - * Sets up a mutation observer for the body element. The mutation observer is used - * to ensure that the inline menu elements are always present at the bottom of the - * body element. + * Sets up a mutation observer for the element which contains the inline menu. */ - private observeBodyElement() { - this.bodyElementMutationObserver?.observe(globalThis.document.body, { childList: true }); + private observeContainerElement(element: HTMLElement) { + this.containerElementMutationObserver?.observe(element, { childList: true }); } /** - * Disconnects the mutation observer for the body element. + * Disconnects the mutation observer for the element which contains the inline menu. */ - private unobserveBodyElement() { - this.bodyElementMutationObserver?.disconnect(); + private unobserveContainerElement() { + this.containerElementMutationObserver?.disconnect(); } /** @@ -370,11 +375,11 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte } /** - * Handles the mutation observer update for the body element. This method will - * ensure that the inline menu elements are always present at the bottom of the - * body element. + * Handles the mutation observer update for the element that contains the inline menu. + * This method will ensure that the inline menu elements are always present at the + * bottom of the container. */ - private handleBodyElementMutationObserverUpdate = () => { + private handleContainerElementMutationObserverUpdate = (mutations: MutationRecord[]) => { if ( (!this.buttonElement && !this.listElement) || this.isTriggeringExcessiveMutationObserverIterations() @@ -382,15 +387,18 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte return; } - requestIdleCallbackPolyfill(this.processBodyElementMutation, { timeout: 500 }); + const containerElement = mutations[0].target as HTMLElement; + requestIdleCallbackPolyfill(() => this.processContainerElementMutation(containerElement), { + timeout: 500, + }); }; /** - * Processes the mutation of the body element. Will trigger when an + * Processes the mutation of the element that contains the inline menu. Will trigger when an * idle moment in the execution of the main thread is detected. */ - private processBodyElementMutation = async () => { - const lastChild = globalThis.document.body.lastElementChild; + private processContainerElementMutation = async (containerElement: HTMLElement) => { + const lastChild = containerElement.lastElementChild; const secondToLastChild = lastChild?.previousElementSibling; const lastChildIsInlineMenuList = lastChild === this.listElement; const lastChildIsInlineMenuButton = lastChild === this.buttonElement; @@ -424,11 +432,11 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte (lastChildIsInlineMenuList && !secondToLastChildIsInlineMenuButton) || (lastChildIsInlineMenuButton && isInlineMenuListVisible) ) { - globalThis.document.body.insertBefore(this.buttonElement, this.listElement); + containerElement.insertBefore(this.buttonElement, this.listElement); return; } - globalThis.document.body.insertBefore(lastChild, this.buttonElement); + containerElement.insertBefore(lastChild, this.buttonElement); }; /** diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.html b/apps/browser/src/autofill/popup/fido2/fido2.component.html index dc9f3ff83ff..80ea6726cb9 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.html +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.html @@ -49,17 +49,22 @@

{{ "chooseCipherForPasskeySave" | i18n }}

- {{ "noMatchingLoginsForSite" | i18n }} - {{ "searchSavePasskeyNewLogin" | i18n }} + {{ + (hasSearched ? "noItemsMatchSearch" : "noMatchingLoginsForSite") | i18n + }} + {{ + (hasSearched ? "searchSavePasskeyNewLogin" : "clearFiltersOrTryAnother") | i18n + }} + @@ -100,17 +105,22 @@

{{ "chooseCipherForPasskeySave" | i18n }}

- {{ "noItemsMatchSearch" | i18n }} - {{ "clearFiltersOrTryAnother" | i18n }} + {{ + (hasSearched ? "noItemsMatchSearch" : "noMatchingLoginsForSite") | i18n + }} + {{ + (hasSearched ? "searchSavePasskeyNewLogin" : "clearFiltersOrTryAnother") | i18n + }} + diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index cf0fd90a8fd..82be95ea0da 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -91,7 +91,6 @@ interface ViewData { export class Fido2Component implements OnInit, OnDestroy { private destroy$ = new Subject(); private message$ = new BehaviorSubject(null); - private hasSearched = false; protected BrowserFido2MessageTypes = BrowserFido2MessageTypes; protected cipher: CipherView; protected ciphers?: CipherView[] = []; @@ -104,6 +103,7 @@ export class Fido2Component implements OnInit, OnDestroy { protected noResultsIcon = Icons.NoResults; protected passkeyAction: PasskeyActionValue = PasskeyActions.Register; protected PasskeyActions = PasskeyActions; + protected hasSearched = false; protected searchText: string; protected searchTypeSearch = false; protected senderTabId?: string; @@ -370,19 +370,30 @@ export class Fido2Component implements OnInit, OnDestroy { return this.equivalentDomains; } + async clearSearch() { + this.searchText = ""; + await this.setDisplayedCiphersToAllDomainMatch(); + } + + protected async setDisplayedCiphersToAllDomainMatch() { + const equivalentDomains = await this.getEquivalentDomains(); + this.displayedCiphers = this.ciphers.filter((cipher) => + cipher.login.matchesUri(this.url, equivalentDomains), + ); + } + protected async search() { - this.hasSearched = await this.searchService.isSearchable(this.searchText); - if (this.hasSearched) { + this.hasSearched = true; + const isSearchable = await this.searchService.isSearchable(this.searchText); + + if (isSearchable) { this.displayedCiphers = await this.searchService.searchCiphers( this.searchText, null, this.ciphers, ); } else { - const equivalentDomains = await this.getEquivalentDomains(); - this.displayedCiphers = this.ciphers.filter((cipher) => - cipher.login.matchesUri(this.url, equivalentDomains), - ); + await this.setDisplayedCiphersToAllDomainMatch(); } } diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html index 9c7047c4cb7..530519e88f1 100644 --- a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html @@ -41,8 +41,45 @@

- + +
+
+
+ + +
+
+ +
@@ -60,7 +97,7 @@

/> -

- + {{ "autofillSuggestionsSectionTitle" | i18n }} > {{ "showInlineMenuOnFormFieldsDescAlt" | i18n }} - - - - - {{ "showInlineMenuOnIconSelectionLabel" | i18n }} - - + {{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }} {{ "autofillSuggestionsSectionTitle" | i18n }} + + + + {{ "showInlineMenuIdentitiesLabel" | i18n }} + + + + + + {{ "showInlineMenuCardsLabel" | i18n }} + + + + + + {{ "showInlineMenuOnIconSelectionLabel" | i18n }} + + { it("updates the inlineMenuVisibility property", () => { sendMockExtensionMessage({ command: "updateAutofillInlineMenuVisibility", - data: { inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick }, + data: { newSettingValue: AutofillOverlayVisibility.OnButtonClick }, }); expect(autofillOverlayContentService["inlineMenuVisibility"]).toEqual( 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 2e85fa22819..4109662fd66 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -8,7 +8,9 @@ import { AutofillOverlayVisibility, AUTOFILL_OVERLAY_HANDLE_REPOSITION, AUTOFILL_TRIGGER_FORM_FIELD_SUBMIT, + AUTOFILL_OVERLAY_HANDLE_SCROLL, } from "@bitwarden/common/autofill/constants"; +import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { CipherType } from "@bitwarden/common/vault/enums"; import { @@ -51,7 +53,9 @@ import { AutoFillConstants } from "./autofill-constants"; export class AutofillOverlayContentService implements AutofillOverlayContentServiceInterface { pageDetailsUpdateRequired = false; - inlineMenuVisibility: number; + inlineMenuVisibility: InlineMenuVisibilitySetting; + private showInlineMenuIdentities: boolean; + private showInlineMenuCards: boolean; private readonly findTabs = tabbable; private readonly sendExtensionMessage = sendExtensionMessage; private formFieldElements: Map, AutofillField> = new Map(); @@ -183,6 +187,18 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ autofillFieldData: AutofillField, pageDetails: AutofillPageDetails, ) { + if (!this.inlineMenuVisibility) { + await this.getInlineMenuVisibility(); + } + + if (this.showInlineMenuCards == null) { + await this.getInlineMenuCardsVisibility(); + } + + if (this.showInlineMenuIdentities == null) { + await this.getInlineMenuIdentitiesVisibility(); + } + if ( this.formFieldElements.has(formFieldElement) || this.isIgnoredField(autofillFieldData, pageDetails) @@ -1019,10 +1035,16 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ const { width, height, top, left } = await this.getMostRecentlyFocusedFieldRects(formFieldElement); const autofillFieldData = this.formFieldElements.get(formFieldElement); + let accountCreationFieldType = null; + if ( + // user setting allows display of identities in inline menu + this.showInlineMenuIdentities && + // `showInlineMenuAccountCreation` has been set or field is filled by Login cipher (autofillFieldData?.showInlineMenuAccountCreation || autofillFieldData?.filledByCipherType === CipherType.Login) && + // field is a username field, which is relevant to both Identity and Login ciphers this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData) ) { accountCreationFieldType = this.inlineMenuFieldQualificationService.isEmailField( @@ -1125,6 +1147,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ } if ( + this.showInlineMenuCards && this.inlineMenuFieldQualificationService.isFieldForCreditCardForm( autofillFieldData, pageDetails, @@ -1146,6 +1169,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ } if ( + this.showInlineMenuIdentities && this.inlineMenuFieldQualificationService.isFieldForIdentityForm( autofillFieldData, pageDetails, @@ -1244,6 +1268,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ autofillFieldData.readonly = getAttributeBoolean(formFieldElement, "disabled"); autofillFieldData.disabled = getAttributeBoolean(formFieldElement, "disabled"); autofillFieldData.viewable = true; + void this.setupOverlayListenersOnQualifiedField(formFieldElement, autofillFieldData); } @@ -1266,10 +1291,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ await this.updateMostRecentlyFocusedField(formFieldElement); } - if (!this.inlineMenuVisibility) { - await this.getInlineMenuVisibility(); - } - this.setupFormFieldElementEventListeners(formFieldElement); this.setupFormSubmissionEventListeners(formFieldElement, autofillFieldData); @@ -1291,6 +1312,30 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ this.inlineMenuVisibility = inlineMenuVisibility || AutofillOverlayVisibility.OnFieldFocus; } + /** + * Queries the background script for the autofill inline menu's Cards visibility setting. + * If the setting is not found, a default value of true will be used + * @private + */ + private async getInlineMenuCardsVisibility() { + const inlineMenuCardsVisibility = await this.sendExtensionMessage( + "getInlineMenuCardsVisibility", + ); + this.showInlineMenuCards = inlineMenuCardsVisibility ?? true; + } + + /** + * Queries the background script for the autofill inline menu's Identities visibility setting. + * If the setting is not found, a default value of true will be used + * @private + */ + private async getInlineMenuIdentitiesVisibility() { + const inlineMenuIdentitiesVisibility = await this.sendExtensionMessage( + "getInlineMenuIdentitiesVisibility", + ); + this.showInlineMenuIdentities = inlineMenuIdentitiesVisibility ?? true; + } + /** * Returns a value that indicates if we should hide the inline menu list due to a filled field. * @@ -1318,8 +1363,10 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ * @param data - The data object from the extension message. */ private updateInlineMenuVisibility({ data }: AutofillExtensionMessage) { - if (!isNaN(data?.inlineMenuVisibility)) { - this.inlineMenuVisibility = data.inlineMenuVisibility; + const newSettingValue = data?.newSettingValue; + + if (!isNaN(newSettingValue)) { + this.inlineMenuVisibility = newSettingValue; } } @@ -1600,15 +1647,28 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ * the overlay elements on scroll or resize. */ private setOverlayRepositionEventListeners() { - const handler = this.useEventHandlersMemo( + const repositionHandler = this.useEventHandlersMemo( throttle(this.handleOverlayRepositionEvent, 250), AUTOFILL_OVERLAY_HANDLE_REPOSITION, ); - globalThis.addEventListener(EVENTS.SCROLL, handler, { + + const eventTargetDoesNotContainFocusedField = (element: Element) => + typeof element?.contains === "function" && !element.contains(this.mostRecentlyFocusedField); + const scrollHandler = this.useEventHandlersMemo( + throttle((event) => { + if (eventTargetDoesNotContainFocusedField(event.target as Element)) { + return; + } + repositionHandler(event); + }, 50), + AUTOFILL_OVERLAY_HANDLE_SCROLL, + ); + + globalThis.addEventListener(EVENTS.SCROLL, scrollHandler, { capture: true, passive: true, }); - globalThis.addEventListener(EVENTS.RESIZE, handler); + globalThis.addEventListener(EVENTS.RESIZE, repositionHandler); } /** @@ -1616,12 +1676,19 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ * the overlay elements on scroll or resize. */ private removeOverlayRepositionEventListeners() { - const handler = this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_REPOSITION]; - globalThis.removeEventListener(EVENTS.SCROLL, handler, { - capture: true, - }); - globalThis.removeEventListener(EVENTS.RESIZE, handler); + globalThis.removeEventListener( + EVENTS.SCROLL, + this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_SCROLL], + { + capture: true, + }, + ); + globalThis.removeEventListener( + EVENTS.RESIZE, + this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_REPOSITION], + ); + delete this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_SCROLL]; delete this.eventHandlersMemo[AUTOFILL_OVERLAY_HANDLE_REPOSITION]; } diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 7bd08caaf33..3f33caccc41 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -75,6 +75,8 @@ describe("AutofillService", () => { let autofillService: AutofillService; const cipherService = mock(); let inlineMenuVisibilityMock$!: BehaviorSubject; + let showInlineMenuCardsMock$!: BehaviorSubject; + let showInlineMenuIdentitiesMock$!: BehaviorSubject; let autofillSettingsService: MockProxy; const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -98,8 +100,12 @@ describe("AutofillService", () => { beforeEach(() => { scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus); + showInlineMenuCardsMock$ = new BehaviorSubject(false); + showInlineMenuIdentitiesMock$ = new BehaviorSubject(false); autofillSettingsService = mock(); autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$; + autofillSettingsService.showInlineMenuCards$ = showInlineMenuCardsMock$; + autofillSettingsService.showInlineMenuIdentities$ = showInlineMenuIdentitiesMock$; activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; @@ -291,12 +297,12 @@ describe("AutofillService", () => { expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( tab1, "updateAutofillInlineMenuVisibility", - { inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick }, + { newSettingValue: AutofillOverlayVisibility.OnButtonClick }, ); expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( tab2, "updateAutofillInlineMenuVisibility", - { inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick }, + { newSettingValue: AutofillOverlayVisibility.OnButtonClick }, ); }); @@ -308,12 +314,12 @@ describe("AutofillService", () => { expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( tab1, "updateAutofillInlineMenuVisibility", - { inlineMenuVisibility: AutofillOverlayVisibility.OnFieldFocus }, + { newSettingValue: AutofillOverlayVisibility.OnFieldFocus }, ); expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( tab2, "updateAutofillInlineMenuVisibility", - { inlineMenuVisibility: AutofillOverlayVisibility.OnFieldFocus }, + { newSettingValue: AutofillOverlayVisibility.OnFieldFocus }, ); }); }); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index ea68b80e84f..0b25426728e 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -130,10 +130,23 @@ export default class AutofillService implements AutofillServiceInterface { async loadAutofillScriptsOnInstall() { BrowserApi.addListener(chrome.runtime.onConnect, this.handleInjectedScriptPortConnection); void this.injectAutofillScriptsInAllTabs(); + this.autofillSettingsService.inlineMenuVisibility$ .pipe(startWith(undefined), pairwise()) .subscribe(([previousSetting, currentSetting]) => - this.handleInlineMenuVisibilityChange(previousSetting, currentSetting), + this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting), + ); + + this.autofillSettingsService.showInlineMenuCards$ + .pipe(startWith(undefined), pairwise()) + .subscribe(([previousSetting, currentSetting]) => + this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting), + ); + + this.autofillSettingsService.showInlineMenuIdentities$ + .pipe(startWith(undefined), pairwise()) + .subscribe(([previousSetting, currentSetting]) => + this.handleInlineMenuVisibilitySettingsChange(previousSetting, currentSetting), ); } @@ -3043,27 +3056,36 @@ export default class AutofillService implements AutofillServiceInterface { } /** - * Updates the autofill inline menu visibility setting in all active tabs - * when the InlineMenuVisibilitySetting observable is updated. + * Updates the autofill inline menu visibility settings in all active tabs + * when the inlineMenuVisibility, showInlineMenuCards, or showInlineMenuIdentities + * observables are updated. * - * @param previousSetting - The previous setting value - * @param currentSetting - The current setting value + * @param oldSettingValue - The previous setting value + * @param newSettingValue - The current setting value + * @param cipherType - The cipher type of the changed inline menu setting */ - private async handleInlineMenuVisibilityChange( - previousSetting: InlineMenuVisibilitySetting, - currentSetting: InlineMenuVisibilitySetting, + private async handleInlineMenuVisibilitySettingsChange( + oldSettingValue: InlineMenuVisibilitySetting | boolean, + newSettingValue: InlineMenuVisibilitySetting | boolean, ) { - if (previousSetting === undefined || previousSetting === currentSetting) { + if (oldSettingValue === undefined || oldSettingValue === newSettingValue) { return; } - const inlineMenuPreviouslyDisabled = previousSetting === AutofillOverlayVisibility.Off; - const inlineMenuCurrentlyDisabled = currentSetting === AutofillOverlayVisibility.Off; - if (!inlineMenuPreviouslyDisabled && !inlineMenuCurrentlyDisabled) { + const isInlineMenuVisibilitySubSetting = + typeof oldSettingValue === "boolean" || typeof newSettingValue === "boolean"; + const inlineMenuPreviouslyDisabled = oldSettingValue === AutofillOverlayVisibility.Off; + const inlineMenuCurrentlyDisabled = newSettingValue === AutofillOverlayVisibility.Off; + + if ( + !isInlineMenuVisibilitySubSetting && + !inlineMenuPreviouslyDisabled && + !inlineMenuCurrentlyDisabled + ) { const tabs = await BrowserApi.tabsQuery({}); tabs.forEach((tab) => BrowserApi.tabSendMessageData(tab, "updateAutofillInlineMenuVisibility", { - inlineMenuVisibility: currentSetting, + newSettingValue, }), ); return; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 318b856b324..e5a4087510c 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -177,6 +177,10 @@ import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitw import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + CipherAuthorizationService, + DefaultCipherAuthorizationService, +} from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; @@ -369,6 +373,7 @@ export default class MainBackground { themeStateService: DefaultThemeStateService; autoSubmitLoginBackground: AutoSubmitLoginBackground; sdkService: SdkService; + cipherAuthorizationService: CipherAuthorizationService; onUpdatedRan: boolean; onReplacedRan: boolean; @@ -731,6 +736,9 @@ export default class MainBackground { sdkClientFactory, this.environmentService, this.platformUtilsService, + this.accountService, + this.kdfConfigService, + this.cryptoService, this.apiService, ); @@ -1262,6 +1270,11 @@ export default class MainBackground { } this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService); + + this.cipherAuthorizationService = new DefaultCipherAuthorizationService( + this.collectionService, + this.organizationService, + ); } async bootstrap() { @@ -1340,7 +1353,7 @@ export default class MainBackground { } if (!supported) { - this.sdkService.failedToInitialize().catch(this.logService.error); + this.sdkService.failedToInitialize().catch((e) => this.logService.error(e)); } } diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 1e4e28ea6d0..a6d91d01878 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -20,9 +20,11 @@ import { LockV2Component, PasswordHintComponent, RegistrationFinishComponent, + RegistrationLockAltIcon, RegistrationStartComponent, RegistrationStartSecondaryComponent, RegistrationStartSecondaryComponentData, + RegistrationUserAddIcon, SetPasswordJitComponent, UserLockIcon, } from "@bitwarden/auth/angular"; @@ -98,6 +100,7 @@ import { ViewComponent } from "../vault/popup/components/vault/view.component"; import { AddEditV2Component } from "../vault/popup/components/vault-v2/add-edit/add-edit-v2.component"; import { AssignCollections } from "../vault/popup/components/vault-v2/assign-collections/assign-collections.component"; import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attachments/attachments-v2.component"; +import { PasswordHistoryV2Component } from "../vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component"; import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component"; import { AppearanceV2Component } from "../vault/popup/settings/appearance-v2.component"; import { AppearanceComponent } from "../vault/popup/settings/appearance.component"; @@ -257,12 +260,11 @@ const routes: Routes = [ canActivate: [authGuard], data: { state: "view-cipher" } satisfies RouteDataProperties, }), - { + ...extensionRefreshSwap(PasswordHistoryComponent, PasswordHistoryV2Component, { path: "cipher-password-history", - component: PasswordHistoryComponent, canActivate: [authGuard], data: { state: "cipher-password-history" } satisfies RouteDataProperties, - }, + }), ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { path: "add-cipher", canActivate: [authGuard, debounceNavigationGuard()], @@ -422,8 +424,12 @@ const routes: Routes = [ path: "hint", canActivate: [unauthGuardFn(unauthRouteOverrides)], data: { - pageTitle: "requestPasswordHint", - pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou", + pageTitle: { + key: "requestPasswordHint", + }, + pageSubtitle: { + key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou", + }, pageIcon: UserLockIcon, showBackButton: true, state: "hint", @@ -443,40 +449,18 @@ const routes: Routes = [ { path: "", component: ExtensionAnonLayoutWrapperComponent, - children: [ - { - path: "lockV2", - canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh), lockGuard()], - data: { - pageIcon: LockIcon, - pageTitle: { - key: "yourVaultIsLockedV2", - }, - showReadonlyHostname: true, - showAcctSwitcher: true, - } satisfies ExtensionAnonLayoutWrapperData, - children: [ - { - path: "", - component: LockV2Component, - }, - ], - }, - ], - }, - { - path: "", - component: AnonLayoutWrapperComponent, children: [ { path: "signup", canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], data: { state: "signup", + pageIcon: RegistrationUserAddIcon, pageTitle: { key: "createAccount", }, - } satisfies RouteDataProperties & AnonLayoutWrapperData, + showBackButton: true, + } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, children: [ { path: "", @@ -496,14 +480,10 @@ const routes: Routes = [ path: "finish-signup", canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], data: { - pageTitle: { - key: "setAStrongPassword", - }, - pageSubtitle: { - key: "finishCreatingYourAccountBySettingAPassword", - }, + pageIcon: RegistrationLockAltIcon, state: "finish-signup", - } satisfies RouteDataProperties & AnonLayoutWrapperData, + showBackButton: true, + } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, children: [ { path: "", @@ -511,6 +491,30 @@ const routes: Routes = [ }, ], }, + { + path: "lockV2", + canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh), lockGuard()], + data: { + pageIcon: LockIcon, + pageTitle: { + key: "yourVaultIsLockedV2", + }, + showReadonlyHostname: true, + showAcctSwitcher: true, + } satisfies ExtensionAnonLayoutWrapperData, + children: [ + { + path: "", + component: LockV2Component, + }, + ], + }, + ], + }, + { + path: "", + component: AnonLayoutWrapperComponent, + children: [ { path: "set-password-jit", canActivate: [canAccessFeature(FeatureFlag.EmailVerification)], diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 113cd736c6a..3c8752f3a76 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -81,7 +81,7 @@ export class AppComponent implements OnInit, OnDestroy { .subscribe((supported) => { if (!supported) { this.logService.debug("SDK is not supported"); - this.sdkService.failedToInitialize().catch(this.logService.error); + this.sdkService.failedToInitialize().catch((e) => this.logService.error(e)); } else { this.logService.debug("SDK is supported"); } diff --git a/apps/browser/src/tools/popup/generator/credential-generator.component.ts b/apps/browser/src/tools/popup/generator/credential-generator.component.ts index 3285a1c4906..9c1af07efdd 100644 --- a/apps/browser/src/tools/popup/generator/credential-generator.component.ts +++ b/apps/browser/src/tools/popup/generator/credential-generator.component.ts @@ -1,4 +1,5 @@ import { Component } from "@angular/core"; +import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ItemModule } from "@bitwarden/components"; @@ -22,6 +23,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopupHeaderComponent, PopupPageComponent, PopupFooterComponent, + RouterModule, ItemModule, ], }) diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts index 20b472f97f3..585f6067e3d 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts @@ -114,8 +114,8 @@ export class SendAddEditComponent { /** * Handles the event when the send is updated. */ - onSendUpdated(send: SendView) { - this.location.back(); + async onSendUpdated(_: SendView) { + await this.router.navigate(["/tabs/send"]); } deleteSend = async () => { @@ -174,18 +174,25 @@ export class SendAddEditComponent { ) .subscribe((config) => { this.config = config; - this.headerText = this.getHeaderText(config.mode); + this.headerText = this.getHeaderText(config.mode, config.sendType); }); } /** - * Gets the header text based on the mode. + * Gets the header text based on the mode and type. * @param mode The mode of the send form. + * @param type The type of the send * @returns The header text. */ - private getHeaderText(mode: SendFormMode) { - return this.i18nService.t( - mode === "edit" || mode === "partial-edit" ? "editSend" : "createSend", - ); + private getHeaderText(mode: SendFormMode, type: SendType) { + const headerKey = + mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader"; + + switch (type) { + case SendType.Text: + return this.i18nService.t(headerKey, this.i18nService.t("textSend")); + case SendType.File: + return this.i18nService.t(headerKey, this.i18nService.t("fileSend")); + } } } diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html index 7c65cbeb17d..af3abbf5427 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html @@ -4,7 +4,7 @@ slot="header" [pageTitle]="'createdSend' | i18n" showBackButton - [backAction]="close.bind(this)" + [backAction]="goToEditSend.bind(this)" > @@ -15,7 +15,9 @@ class="tw-flex tw-bg-background-alt tw-flex-col tw-justify-center tw-items-center tw-gap-2 tw-h-full tw-px-5" > -

{{ "createdSendSuccessfully" | i18n }}

+

+ {{ "createdSendSuccessfully" | i18n }} +

{{ formatExpirationDate() }}

@@ -27,7 +29,7 @@

{{ "createdSendSuccessfully" | i18n }}

- diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts index 24186ad4275..fdf147b360f 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts @@ -11,6 +11,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { ButtonModule, I18nMockService, IconModule, ToastService } from "@bitwarden/components"; @@ -50,6 +51,7 @@ describe("SendCreatedComponent", () => { sendView = { id: sendId, deletionDate: new Date(), + type: SendType.Text, accessId: "abc", urlB64Key: "123", } as SendView; @@ -129,9 +131,11 @@ describe("SendCreatedComponent", () => { expect(component["hoursAvailable"]).toBe(0); }); - it("should navigate back to send list on close", async () => { - await component.close(); - expect(router.navigate).toHaveBeenCalledWith(["/tabs/send"]); + it("should navigate back to the edit send form on close", async () => { + await component.goToEditSend(); + expect(router.navigate).toHaveBeenCalledWith(["/edit-send"], { + queryParams: { sendId: "test-send-id", type: SendType.Text }, + }); }); describe("getHoursAvailable", () => { diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts index ae66d14d3f0..98b09d380e4 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts @@ -65,11 +65,11 @@ export class SendCreatedComponent { if (this.hoursAvailable < 24) { return this.hoursAvailable === 1 ? this.i18nService.t("sendExpiresInHoursSingle") - : this.i18nService.t("sendExpiresInHours", this.hoursAvailable); + : this.i18nService.t("sendExpiresInHours", String(this.hoursAvailable)); } return this.daysAvailable === 1 ? this.i18nService.t("sendExpiresInDaysSingle") - : this.i18nService.t("sendExpiresInDays", this.daysAvailable); + : this.i18nService.t("sendExpiresInDays", String(this.daysAvailable)); } getHoursAvailable(send: SendView): number { @@ -77,7 +77,13 @@ export class SendCreatedComponent { return Math.max(0, Math.ceil((send.deletionDate.getTime() - now) / (1000 * 60 * 60))); } - async close() { + async goToEditSend() { + await this.router.navigate([`/edit-send`], { + queryParams: { sendId: this.send.id, type: this.send.type }, + }); + } + + async goBack() { await this.router.navigate(["/tabs/send"]); } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html index 7652b8ab0bf..72aaeea493d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.html @@ -1,4 +1,4 @@ - + + + + + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts new file mode 100644 index 00000000000..a375aba302e --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts @@ -0,0 +1,56 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ActivatedRoute } from "@angular/router"; +import { mock } from "jest-mock-extended"; +import { Subject } from "rxjs"; + +import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; + +import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; + +import { PasswordHistoryV2Component } from "./vault-password-history-v2.component"; + +describe("PasswordHistoryV2Component", () => { + let component: PasswordHistoryV2Component; + let fixture: ComponentFixture; + const params$ = new Subject(); + const back = jest.fn().mockResolvedValue(undefined); + + beforeEach(async () => { + back.mockClear(); + + await TestBed.configureTestingModule({ + imports: [PasswordHistoryV2Component], + providers: [ + { provide: WINDOW, useValue: window }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: ConfigService, useValue: mock() }, + { provide: CipherService, useValue: mock() }, + { provide: AccountService, useValue: mock() }, + { provide: PopupRouterCacheService, useValue: { back } }, + { provide: ActivatedRoute, useValue: { queryParams: params$ } }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(PasswordHistoryV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("sets the cipherId from the params", () => { + params$.next({ cipherId: "444-33-33-1111" }); + + expect(component["cipherId"]).toBe("444-33-33-1111"); + }); + + it("navigates back when a cipherId is not in the params", () => { + params$.next({}); + + expect(back).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts new file mode 100644 index 00000000000..bc677a91d64 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -0,0 +1,50 @@ +import { NgIf } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { first } from "rxjs/operators"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { CipherId } from "@bitwarden/common/types/guid"; + +import { PasswordHistoryViewComponent } from "../../../../../../../../libs/vault/src/components/password-history-view/password-history-view.component"; +import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; +import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; + +@Component({ + standalone: true, + selector: "vault-password-history-v2", + templateUrl: "vault-password-history-v2.component.html", + imports: [ + JslibModule, + PopupPageComponent, + PopOutComponent, + PopupHeaderComponent, + PasswordHistoryViewComponent, + NgIf, + ], +}) +export class PasswordHistoryV2Component implements OnInit { + protected cipherId: CipherId; + + constructor( + private browserRouterHistory: PopupRouterCacheService, + private route: ActivatedRoute, + ) {} + + ngOnInit() { + // eslint-disable-next-line rxjs-angular/prefer-takeuntil + this.route.queryParams.pipe(first()).subscribe((params) => { + if (params.cipherId) { + this.cipherId = params.cipherId; + } else { + this.close(); + } + }); + } + + close() { + void this.browserRouterHistory.back(); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html index a778d6aaea9..c2645f15ea8 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html @@ -28,7 +28,7 @@ -
+
- - -
+

{{ "additionalOptions" | i18n }}

@@ -302,13 +262,50 @@

{{ "additionalOptions" | i18n }}

- -
- - {{ - "manageBillingFromProviderPortalMessage" | i18n - }} -
-
+ + + +

{{ "manageSubscription" | i18n }}

+

+ {{ "manageSubscriptionFromThe" | i18n }} + {{ + "providerPortal" | i18n + }}. +

+ +

+ {{ "billingManagedByProvider" | i18n: userOrg.providerName }}. + {{ "billingContactProviderForAssistance" | i18n }}. +

+
+ +
+ +
+ +

{{ "billingManagedByProvider" | i18n: userOrg.providerName }}

+

{{ "billingContactProviderForAssistance" | i18n }}

+
+
+
+
+ + + +

+ {{ "selfHostingTitleProper" | i18n }} +

+

+ {{ "toHostBitwardenOnYourOwnServer" | i18n }} +

+
+ + +
+
+
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 7a66faa0a43..e604140e569 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -5,9 +5,9 @@ import { concatMap, firstValueFrom, lastValueFrom, Observable, Subject, takeUnti import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { OrganizationApiKeyType, ProviderStatusType } from "@bitwarden/common/admin-console/enums"; +import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response"; @@ -15,7 +15,6 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { @@ -34,7 +33,7 @@ import { import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; import { ChangePlanDialogResultType, openChangePlanDialog } from "./change-plan-dialog.component"; import { DownloadLicenceDialogComponent } from "./download-license.component"; -import { ManageBilling } from "./icons/manage-billing.icon"; +import { SubscriptionHiddenIcon } from "./icons/subscription-hidden.icon"; import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.component"; @Component({ @@ -50,19 +49,17 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy hasBillingSyncToken: boolean; showAdjustSecretsManager = false; showSecretsManagerSubscribe = false; - firstLoaded = false; - loading: boolean; + loading = true; locale: string; showUpdatedSubscriptionStatusSection$: Observable; - manageBillingFromProviderPortal = ManageBilling; - isManagedByConsolidatedBillingMSP = false; enableTimeThreshold: boolean; preSelectedProductTier: ProductTierType = ProductTierType.Free; + showSubscription = true; + showSelfHost = false; + protected readonly subscriptionHiddenIcon = SubscriptionHiddenIcon; protected readonly teamsStarter = ProductTierType.TeamsStarter; - private destroy$ = new Subject(); - protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$( FeatureFlag.EnableConsolidatedBilling, ); @@ -71,7 +68,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy FeatureFlag.EnableTimeThreshold, ); - protected EnableUpgradePasswordManagerSub$ = this.configService.getFeatureFlag$( + protected enableUpgradePasswordManagerSub$ = this.configService.getFeatureFlag$( FeatureFlag.EnableUpgradePasswordManagerSub, ); @@ -79,9 +76,10 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy FeatureFlag.AC2476_DeprecateStripeSourcesAPI, ); + private destroy$ = new Subject(); + constructor( private apiService: ApiService, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private logService: LogService, private organizationService: OrganizationService, @@ -89,15 +87,13 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy private route: ActivatedRoute, private dialogService: DialogService, private configService: ConfigService, - private providerService: ProviderService, private toastService: ToastService, + private billingApiService: BillingApiServiceAbstraction, ) {} async ngOnInit() { if (this.route.snapshot.queryParamMap.get("upgrade")) { - // 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.changePlan(); + await this.changePlan(); const productTierTypeStr = this.route.snapshot.queryParamMap.get("productTierType"); if (productTierTypeStr != null) { const productTier = Number(productTierTypeStr); @@ -112,7 +108,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy concatMap(async (params) => { this.organizationId = params.organizationId; await this.load(); - this.firstLoaded = true; }), takeUntil(this.destroy$), ) @@ -130,21 +125,34 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } async load() { - if (this.loading) { - return; - } this.loading = true; this.locale = await firstValueFrom(this.i18nService.locale$); this.userOrg = await this.organizationService.get(this.organizationId); - if (this.userOrg.canViewSubscription) { - const enableConsolidatedBilling = await firstValueFrom(this.enableConsolidatedBilling$); - const provider = await this.providerService.get(this.userOrg.providerId); - this.isManagedByConsolidatedBillingMSP = - enableConsolidatedBilling && - this.userOrg.hasProvider && - provider?.providerStatus == ProviderStatusType.Billable; + /* + +--------------------+--------------+----------------------+--------------+ + | User Type | Has Provider | Consolidated Billing | Subscription | + +--------------------+--------------+----------------------+--------------+ + | Organization Owner | False | N/A | Shown | + | Organization Owner | True | N/A | Hidden | + | Provider User | True | False | Shown | + | Provider User | True | True | Hidden | + +--------------------+--------------+----------------------+--------------+ + */ + + const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$); + this.showSubscription = + (!this.userOrg.hasProvider && this.userOrg.isOwner) || + (this.userOrg.hasProvider && this.userOrg.isProviderUser && !consolidatedBillingEnabled); + + const metadata = await this.billingApiService.getOrganizationBillingMetadata( + this.organizationId, + ); + + this.showSelfHost = metadata.isEligibleForSelfHost; + + if (this.showSubscription) { this.sub = await this.organizationApiService.getSubscription(this.organizationId); this.lineItems = this.sub?.subscription?.items; @@ -277,26 +285,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy return this.sub.subscription?.items.some((i) => i.sponsoredSubscriptionItem); } - get canDownloadLicense() { - return ( - (this.sub.planType !== PlanType.Free && this.subscription == null) || - (this.subscription != null && !this.subscription.cancelled) - ); - } - - get canManageBillingSync() { - return ( - this.sub.planType === PlanType.EnterpriseAnnually || - this.sub.planType === PlanType.EnterpriseMonthly || - this.sub.planType === PlanType.EnterpriseAnnually2023 || - this.sub.planType === PlanType.EnterpriseMonthly2023 || - this.sub.planType === PlanType.EnterpriseAnnually2020 || - this.sub.planType === PlanType.EnterpriseMonthly2020 || - this.sub.planType === PlanType.EnterpriseAnnually2019 || - this.sub.planType === PlanType.EnterpriseMonthly2019 - ); - } - get subscriptionDesc() { if (this.sub.planType === PlanType.Free) { return this.i18nService.t("subscriptionFreePlan", this.sub.seats.toString()); @@ -353,13 +341,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy ); } - shownSelfHost(): boolean { - return ( - this.sub?.plan.productTier !== ProductTierType.Teams && - this.sub?.plan.productTier !== ProductTierType.Free - ); - } - cancelSubscription = async () => { const reference = openOffboardingSurvey(this.dialogService, { data: { @@ -399,9 +380,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy title: null, message: this.i18nService.t("reinstated"), }); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); + await this.load(); } catch (e) { this.logService.error(e); } @@ -409,7 +388,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy async changePlan() { const EnableUpgradePasswordManagerSub = await firstValueFrom( - this.EnableUpgradePasswordManagerSub$, + this.enableUpgradePasswordManagerSub$, ); if (EnableUpgradePasswordManagerSub) { const reference = openChangePlanDialog(this.dialogService, { @@ -458,24 +437,15 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy }); await firstValueFrom(dialogRef.closed); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); - } - - closeDownloadLicense() { - this.showDownloadLicense = false; + await this.load(); } - subscriptionAdjusted() { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.load(); + async subscriptionAdjusted() { + await this.load(); } calculateTotalAppliedDiscount(total: number) { - const discountedTotal = total / (1 - this.customerDiscount?.percentOff / 100); - return discountedTotal; + return total / (1 - this.customerDiscount?.percentOff / 100); } adjustStorage = (add: boolean) => { diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index e7ae154ec4e..8822800b36d 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -25,6 +25,9 @@ import { LockV2Component, LockIcon, UserLockIcon, + RegistrationUserAddIcon, + RegistrationLockAltIcon, + RegistrationExpiredLinkIcon, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -184,7 +187,9 @@ const routes: Routes = [ path: "hint", canActivate: [unauthGuardFn()], data: { - pageTitle: "passwordHint", + pageTitle: { + key: "passwordHint", + }, titleId: "passwordHint", }, children: [ @@ -203,8 +208,12 @@ const routes: Routes = [ path: "hint", canActivate: [unauthGuardFn()], data: { - pageTitle: "requestPasswordHint", - pageSubtitle: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou", + pageTitle: { + key: "requestPasswordHint", + }, + pageSubtitle: { + key: "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou", + }, pageIcon: UserLockIcon, state: "hint", }, @@ -228,6 +237,7 @@ const routes: Routes = [ path: "signup", canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], data: { + pageIcon: RegistrationUserAddIcon, pageTitle: { key: "createAccount", }, @@ -252,12 +262,7 @@ const routes: Routes = [ path: "finish-signup", canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], data: { - pageTitle: { - key: "setAStrongPassword", - }, - pageSubtitle: { - key: "finishCreatingYourAccountBySettingAPassword", - }, + pageIcon: RegistrationLockAltIcon, titleId: "setAStrongPassword", } satisfies RouteDataProperties & AnonLayoutWrapperData, children: [ @@ -304,6 +309,7 @@ const routes: Routes = [ path: "signup-link-expired", canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], data: { + pageIcon: RegistrationExpiredLinkIcon, pageTitle: { key: "expiredLink", }, diff --git a/apps/web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts b/apps/web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts index b35b1fa64a3..88efb2b4832 100644 --- a/apps/web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts +++ b/apps/web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts @@ -1,7 +1,6 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { unauthGuardFn } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -11,7 +10,7 @@ const routes: Routes = [ { path: "", component: AccessIntelligenceComponent, - canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence), unauthGuardFn()], + canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence)], data: { titleId: "accessIntelligence", }, diff --git a/apps/web/src/app/tools/access-intelligence/access-intelligence.component.html b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.html index 665f8f6b0c5..78ddfb23929 100644 --- a/apps/web/src/app/tools/access-intelligence/access-intelligence.component.html +++ b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.html @@ -1,6 +1,12 @@ - + + + + + + + diff --git a/apps/web/src/app/tools/access-intelligence/access-intelligence.component.ts b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.ts index 9e5eff6f629..3444e3a7ff1 100644 --- a/apps/web/src/app/tools/access-intelligence/access-intelligence.component.ts +++ b/apps/web/src/app/tools/access-intelligence/access-intelligence.component.ts @@ -11,6 +11,8 @@ import { HeaderModule } from "../../layouts/header/header.module"; import { ApplicationTableComponent } from "./application-table.component"; import { NotifiedMembersTableComponent } from "./notified-members-table.component"; +import { PasswordHealthMembersComponent } from "./password-health-members.component"; +import { PasswordHealthComponent } from "./password-health.component"; export enum AccessIntelligenceTabType { AllApps = 0, @@ -26,6 +28,8 @@ export enum AccessIntelligenceTabType { CommonModule, JslibModule, HeaderModule, + PasswordHealthComponent, + PasswordHealthMembersComponent, NotifiedMembersTableComponent, TabsModule, ], diff --git a/apps/web/src/app/tools/access-intelligence/password-health-members.component.html b/apps/web/src/app/tools/access-intelligence/password-health-members.component.html new file mode 100644 index 00000000000..f902011110b --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/password-health-members.component.html @@ -0,0 +1,61 @@ + +

{{ "passwordsReportDesc" | i18n }}

+
+ + {{ "loading" | i18n }} +
+
+ + + + + {{ "name" | i18n }} + {{ "weakness" | i18n }} + {{ "timesReused" | i18n }} + {{ "timesExposed" | i18n }} + {{ "totalMembers" | i18n }} + + + + + + + + + + {{ r.name }} + +
+ {{ r.subTitle }} + + + + {{ passwordStrengthMap.get(r.id)[0] | i18n }} + + + + + {{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} + + + + + {{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }} + + + + {{ totalMembersMap.get(r.id) || 0 }} + + +
+
+
+
diff --git a/apps/web/src/app/tools/access-intelligence/password-health-members.component.ts b/apps/web/src/app/tools/access-intelligence/password-health-members.component.ts new file mode 100644 index 00000000000..fd04974b2ce --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/password-health-members.component.ts @@ -0,0 +1,233 @@ +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute } from "@angular/router"; +import { from, map, switchMap, tap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + BadgeModule, + BadgeVariant, + ContainerComponent, + TableDataSource, + TableModule, +} from "@bitwarden/components"; + +// eslint-disable-next-line no-restricted-imports +import { HeaderModule } from "../../layouts/header/header.module"; +// eslint-disable-next-line no-restricted-imports +import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; +// eslint-disable-next-line no-restricted-imports +import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +// eslint-disable-next-line no-restricted-imports +import { cipherData } from "../reports/pages/reports-ciphers.mock"; + +import { userData } from "./password-health.mock"; + +@Component({ + standalone: true, + selector: "tools-password-health-members", + templateUrl: "password-health-members.component.html", + imports: [ + BadgeModule, + OrganizationBadgeModule, + CommonModule, + ContainerComponent, + PipesModule, + JslibModule, + HeaderModule, + TableModule, + ], +}) +export class PasswordHealthMembersComponent implements OnInit { + passwordStrengthMap = new Map(); + + weakPasswordCiphers: CipherView[] = []; + + passwordUseMap = new Map(); + + exposedPasswordMap = new Map(); + + dataSource = new TableDataSource(); + + totalMembersMap = new Map(); + + reportCiphers: CipherView[] = []; + reportCipherIds: string[] = []; + + organization: Organization; + + loading = true; + + private destroyRef = inject(DestroyRef); + + constructor( + protected cipherService: CipherService, + protected passwordStrengthService: PasswordStrengthServiceAbstraction, + protected organizationService: OrganizationService, + protected auditService: AuditService, + protected i18nService: I18nService, + protected activatedRoute: ActivatedRoute, + ) {} + + ngOnInit() { + this.activatedRoute.paramMap + .pipe( + takeUntilDestroyed(this.destroyRef), + map((params) => params.get("organizationId")), + switchMap((organizationId) => { + return from(this.organizationService.get(organizationId)); + }), + tap((organization) => { + this.organization = organization; + }), + switchMap(() => from(this.setCiphers())), + ) + .subscribe(); + + // mock data - will be replaced with actual data + userData.forEach((user) => { + user.cipherIds.forEach((cipherId: string) => { + if (this.totalMembersMap.has(cipherId)) { + this.totalMembersMap.set(cipherId, (this.totalMembersMap.get(cipherId) || 0) + 1); + } else { + this.totalMembersMap.set(cipherId, 1); + } + }); + }); + } + + async setCiphers() { + // const allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id); + const allCiphers = cipherData; + allCiphers.forEach(async (cipher) => { + this.findWeakPassword(cipher); + this.findReusedPassword(cipher); + await this.findExposedPassword(cipher); + }); + this.dataSource.data = this.reportCiphers; + this.loading = false; + } + + protected checkForExistingCipher(ciph: CipherView) { + if (!this.reportCipherIds.includes(ciph.id)) { + this.reportCipherIds.push(ciph.id); + this.reportCiphers.push(ciph); + } + } + + protected async findExposedPassword(cipher: CipherView) { + const { type, login, isDeleted, edit, viewPassword, id } = cipher; + if ( + type !== CipherType.Login || + login.password == null || + login.password === "" || + isDeleted || + (!this.organization && !edit) || + !viewPassword + ) { + return; + } + + const exposedCount = await this.auditService.passwordLeaked(login.password); + if (exposedCount > 0) { + this.exposedPasswordMap.set(id, exposedCount); + this.checkForExistingCipher(cipher); + } + } + + protected findReusedPassword(cipher: CipherView) { + const { type, login, isDeleted, edit, viewPassword } = cipher; + if ( + type !== CipherType.Login || + login.password == null || + login.password === "" || + isDeleted || + (!this.organization && !edit) || + !viewPassword + ) { + return; + } + + if (this.passwordUseMap.has(login.password)) { + this.passwordUseMap.set(login.password, this.passwordUseMap.get(login.password) || 0 + 1); + } else { + this.passwordUseMap.set(login.password, 1); + } + + this.checkForExistingCipher(cipher); + } + + protected findWeakPassword(cipher: CipherView): void { + const { type, login, isDeleted, edit, viewPassword } = cipher; + if ( + type !== CipherType.Login || + login.password == null || + login.password === "" || + isDeleted || + (!this.organization && !edit) || + !viewPassword + ) { + return; + } + + const hasUserName = this.isUserNameNotEmpty(cipher); + let userInput: string[] = []; + if (hasUserName) { + const atPosition = login.username.indexOf("@"); + if (atPosition > -1) { + userInput = userInput + .concat( + login.username + .substring(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/), + ) + .filter((i) => i.length >= 3); + } else { + userInput = login.username + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + .filter((i) => i.length >= 3); + } + } + const { score } = this.passwordStrengthService.getPasswordStrength( + login.password, + null, + userInput.length > 0 ? userInput : null, + ); + + if (score != null && score <= 2) { + this.passwordStrengthMap.set(cipher.id, this.scoreKey(score)); + this.checkForExistingCipher(cipher); + } + } + + private isUserNameNotEmpty(c: CipherView): boolean { + return !Utils.isNullOrWhitespace(c.login.username); + } + + private scoreKey(score: number): [string, BadgeVariant] { + switch (score) { + case 4: + return ["strong", "success"]; + case 3: + return ["good", "primary"]; + case 2: + return ["weak", "warning"]; + default: + return ["veryWeak", "danger"]; + } + } +} diff --git a/apps/web/src/app/tools/access-intelligence/password-health.component.html b/apps/web/src/app/tools/access-intelligence/password-health.component.html new file mode 100644 index 00000000000..32459706449 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/password-health.component.html @@ -0,0 +1,57 @@ + +

{{ "passwordsReportDesc" | i18n }}

+
+ + {{ "loading" | i18n }} +
+
+ + + + + {{ "name" | i18n }} + {{ "weakness" | i18n }} + {{ "timesReused" | i18n }} + {{ "timesExposed" | i18n }} + + + + + + + + + + {{ r.name }} + +
+ {{ r.subTitle }} + + + + {{ passwordStrengthMap.get(r.id)[0] | i18n }} + + + + + {{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} + + + + + {{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }} + + + +
+
+
+
diff --git a/apps/web/src/app/tools/access-intelligence/password-health.component.spec.ts b/apps/web/src/app/tools/access-intelligence/password-health.component.spec.ts new file mode 100644 index 00000000000..4a6d5c50ee1 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/password-health.component.spec.ts @@ -0,0 +1,114 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ActivatedRoute, convertToParamMap } from "@angular/router"; +import { MockProxy, mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { TableModule } from "@bitwarden/components"; +import { TableBodyDirective } from "@bitwarden/components/src/table/table.component"; + +import { LooseComponentsModule } from "../../shared"; +import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; +// eslint-disable-next-line no-restricted-imports +import { cipherData } from "../reports/pages/reports-ciphers.mock"; + +import { PasswordHealthComponent } from "./password-health.component"; + +describe("PasswordHealthComponent", () => { + let component: PasswordHealthComponent; + let fixture: ComponentFixture; + let passwordStrengthService: MockProxy; + let organizationService: MockProxy; + let cipherServiceMock: MockProxy; + let auditServiceMock: MockProxy; + const activeRouteParams = convertToParamMap({ organizationId: "orgId" }); + + beforeEach(async () => { + passwordStrengthService = mock(); + auditServiceMock = mock(); + organizationService = mock({ + get: jest.fn().mockResolvedValue({ id: "orgId" } as Organization), + }); + cipherServiceMock = mock({ + getAllFromApiForOrganization: jest.fn().mockResolvedValue(cipherData), + }); + + await TestBed.configureTestingModule({ + imports: [PasswordHealthComponent, PipesModule, TableModule, LooseComponentsModule], + declarations: [TableBodyDirective], + providers: [ + { provide: CipherService, useValue: cipherServiceMock }, + { provide: PasswordStrengthServiceAbstraction, useValue: passwordStrengthService }, + { provide: OrganizationService, useValue: organizationService }, + { provide: I18nService, useValue: mock() }, + { provide: AuditService, useValue: auditServiceMock }, + { + provide: ActivatedRoute, + useValue: { + paramMap: of(activeRouteParams), + url: of([]), + }, + }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordHealthComponent); + component = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it("should initialize component", () => { + expect(component).toBeTruthy(); + }); + + it("should populate reportCiphers with ciphers that have password issues", async () => { + passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 1 } as any); + + auditServiceMock.passwordLeaked.mockResolvedValue(5); + + await component.setCiphers(); + + const cipherIds = component.reportCiphers.map((c) => c.id); + + expect(cipherIds).toEqual([ + "cbea34a8-bde4-46ad-9d19-b05001228ab1", + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + ]); + expect(component.reportCiphers.length).toEqual(3); + }); + + it("should correctly populate passwordStrengthMap", async () => { + passwordStrengthService.getPasswordStrength.mockImplementation((password) => { + let score = 0; + if (password === "123") { + score = 1; + } else { + score = 4; + } + return { score } as any; + }); + + auditServiceMock.passwordLeaked.mockResolvedValue(0); + + await component.setCiphers(); + + expect(component.passwordStrengthMap.size).toBeGreaterThan(0); + expect(component.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toEqual([ + "veryWeak", + "danger", + ]); + expect(component.passwordStrengthMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toEqual([ + "veryWeak", + "danger", + ]); + }); +}); diff --git a/apps/web/src/app/tools/access-intelligence/password-health.component.ts b/apps/web/src/app/tools/access-intelligence/password-health.component.ts new file mode 100644 index 00000000000..6e8e62c50db --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/password-health.component.ts @@ -0,0 +1,229 @@ +import { CommonModule } from "@angular/common"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute } from "@angular/router"; +import { from, map, switchMap, tap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + BadgeModule, + BadgeVariant, + ContainerComponent, + TableDataSource, + TableModule, +} from "@bitwarden/components"; + +// eslint-disable-next-line no-restricted-imports +import { HeaderModule } from "../../layouts/header/header.module"; +// eslint-disable-next-line no-restricted-imports +import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; +// eslint-disable-next-line no-restricted-imports +import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; + +@Component({ + standalone: true, + selector: "tools-password-health", + templateUrl: "password-health.component.html", + imports: [ + BadgeModule, + OrganizationBadgeModule, + CommonModule, + ContainerComponent, + PipesModule, + JslibModule, + HeaderModule, + TableModule, + ], +}) +export class PasswordHealthComponent implements OnInit { + passwordStrengthMap = new Map(); + + weakPasswordCiphers: CipherView[] = []; + + passwordUseMap = new Map(); + + exposedPasswordMap = new Map(); + + dataSource = new TableDataSource(); + + reportCiphers: CipherView[] = []; + reportCipherIds: string[] = []; + + organization: Organization; + + loading = true; + + private destroyRef = inject(DestroyRef); + + constructor( + protected cipherService: CipherService, + protected passwordStrengthService: PasswordStrengthServiceAbstraction, + protected organizationService: OrganizationService, + protected auditService: AuditService, + protected i18nService: I18nService, + protected activatedRoute: ActivatedRoute, + ) {} + + ngOnInit() { + this.activatedRoute.paramMap + .pipe( + takeUntilDestroyed(this.destroyRef), + map((params) => params.get("organizationId")), + switchMap((organizationId) => { + return from(this.organizationService.get(organizationId)); + }), + tap((organization) => { + this.organization = organization; + }), + switchMap(() => from(this.setCiphers())), + ) + .subscribe(); + } + + async setCiphers() { + const allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organization.id); + allCiphers.forEach(async (cipher) => { + this.findWeakPassword(cipher); + this.findReusedPassword(cipher); + await this.findExposedPassword(cipher); + }); + this.dataSource.data = this.reportCiphers; + this.loading = false; + + // const reportIssues = allCiphers.map((c) => { + // if (this.passwordStrengthMap.has(c.id)) { + // return c; + // } + + // if (this.passwordUseMap.has(c.id)) { + // return c; + // } + + // if (this.exposedPasswordMap.has(c.id)) { + // return c; + // } + // }); + } + + protected checkForExistingCipher(ciph: CipherView) { + if (!this.reportCipherIds.includes(ciph.id)) { + this.reportCipherIds.push(ciph.id); + this.reportCiphers.push(ciph); + } + } + + protected async findExposedPassword(cipher: CipherView) { + const { type, login, isDeleted, edit, viewPassword, id } = cipher; + if ( + type !== CipherType.Login || + login.password == null || + login.password === "" || + isDeleted || + (!this.organization && !edit) || + !viewPassword + ) { + return; + } + + const exposedCount = await this.auditService.passwordLeaked(login.password); + if (exposedCount > 0) { + this.exposedPasswordMap.set(id, exposedCount); + this.checkForExistingCipher(cipher); + } + } + + protected findReusedPassword(cipher: CipherView) { + const { type, login, isDeleted, edit, viewPassword } = cipher; + if ( + type !== CipherType.Login || + login.password == null || + login.password === "" || + isDeleted || + (!this.organization && !edit) || + !viewPassword + ) { + return; + } + + if (this.passwordUseMap.has(login.password)) { + this.passwordUseMap.set(login.password, this.passwordUseMap.get(login.password) || 0 + 1); + } else { + this.passwordUseMap.set(login.password, 1); + } + + this.checkForExistingCipher(cipher); + } + + protected findWeakPassword(cipher: CipherView): void { + const { type, login, isDeleted, edit, viewPassword } = cipher; + if ( + type !== CipherType.Login || + login.password == null || + login.password === "" || + isDeleted || + (!this.organization && !edit) || + !viewPassword + ) { + return; + } + + const hasUserName = this.isUserNameNotEmpty(cipher); + let userInput: string[] = []; + if (hasUserName) { + const atPosition = login.username.indexOf("@"); + if (atPosition > -1) { + userInput = userInput + .concat( + login.username + .substring(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/), + ) + .filter((i) => i.length >= 3); + } else { + userInput = login.username + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + .filter((i) => i.length >= 3); + } + } + const { score } = this.passwordStrengthService.getPasswordStrength( + login.password, + null, + userInput.length > 0 ? userInput : null, + ); + + if (score != null && score <= 2) { + this.passwordStrengthMap.set(cipher.id, this.scoreKey(score)); + this.checkForExistingCipher(cipher); + } + } + + private isUserNameNotEmpty(c: CipherView): boolean { + return !Utils.isNullOrWhitespace(c.login.username); + } + + private scoreKey(score: number): [string, BadgeVariant] { + switch (score) { + case 4: + return ["strong", "success"]; + case 3: + return ["good", "primary"]; + case 2: + return ["weak", "warning"]; + default: + return ["veryWeak", "danger"]; + } + } +} diff --git a/apps/web/src/app/tools/access-intelligence/password-health.mock.ts b/apps/web/src/app/tools/access-intelligence/password-health.mock.ts new file mode 100644 index 00000000000..d01edc37a59 --- /dev/null +++ b/apps/web/src/app/tools/access-intelligence/password-health.mock.ts @@ -0,0 +1,66 @@ +export const userData: any[] = [ + { + userName: "David Brent", + email: "david.brent@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab1", + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Tim Canterbury", + email: "tim.canterbury@wernhamhogg.uk", + usesKeyConnector: false, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Gareth Keenan", + email: "gareth.keenan@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + "cbea34a8-bde4-46ad-9d19-b05001227nm7", + ], + }, + { + userName: "Dawn Tinsley", + email: "dawn.tinsley@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + ], + }, + { + userName: "Keith Bishop", + email: "keith.bishop@wernhamhogg.uk", + usesKeyConnector: false, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab1", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + "cbea34a8-bde4-46ad-9d19-b05001227nm5", + ], + }, + { + userName: "Chris Finch", + email: "chris.finch@wernhamhogg.uk", + usesKeyConnector: true, + cipherIds: [ + "cbea34a8-bde4-46ad-9d19-b05001228ab2", + "cbea34a8-bde4-46ad-9d19-b05001228cd3", + "cbea34a8-bde4-46ad-9d19-b05001228xy4", + ], + }, +]; diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html index 740264713ca..ccf853837c3 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html @@ -72,7 +72,7 @@ buttonType="danger" [appA11yTitle]="'delete' | i18n" [bitAction]="delete" - [disabled]="!canDelete" + [disabled]="!(canDeleteCipher$ | async)" data-testid="delete-cipher-btn" >
diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 38416c2c39c..ae2cf88fd1f 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -2,7 +2,7 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, Subject } from "rxjs"; +import { firstValueFrom, Observable, Subject } from "rxjs"; import { map } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; @@ -12,12 +12,13 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { AsyncActionsModule, ButtonModule, @@ -63,6 +64,16 @@ export interface VaultItemDialogParams { * If true, the "edit" button will be disabled in the dialog. */ disableForm?: boolean; + + /** + * The ID of the active collection. This is know the collection filter selected by the user. + */ + activeCollectionId?: CollectionId; + + /** + * If true, the dialog is being opened from the admin console. + */ + isAdminConsoleAction?: boolean; } export enum VaultItemDialogResult { @@ -204,6 +215,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { protected formConfig: CipherFormConfig = this.params.formConfig; + protected canDeleteCipher$: Observable; + constructor( @Inject(DIALOG_DATA) protected params: VaultItemDialogParams, private dialogRef: DialogRef, @@ -217,6 +230,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { private router: Router, private billingAccountProfileStateService: BillingAccountProfileStateService, private premiumUpgradeService: PremiumUpgradePromptService, + private cipherAuthorizationService: CipherAuthorizationService, ) { this.updateTitle(); } @@ -231,6 +245,12 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this.organization = this.formConfig.organizations.find( (o) => o.id === this.cipher.organizationId, ); + + this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$( + this.cipher, + [this.params.activeCollectionId], + this.params.isAdminConsoleAction, + ); } this.performingInitialLoad = false; diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index 4302edcb4ec..5c4de576ead 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -1,5 +1,6 @@ - + +

+ {{ permissionText }} +

+ - - - -
- - -
-

{{ "noPasswordsInList" | i18n }}

-
+ diff --git a/apps/web/src/app/vault/individual-vault/view.component.spec.ts b/apps/web/src/app/vault/individual-vault/view.component.spec.ts index 0dd58b846d7..d1bfd221175 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.spec.ts @@ -14,6 +14,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { ViewCipherDialogParams, ViewCipherDialogResult, ViewComponent } from "./view.component"; @@ -62,6 +63,12 @@ describe("ViewComponent", () => { useValue: mock(), }, { provide: ConfigService, useValue: mock() }, + { + provide: CipherAuthorizationService, + useValue: { + canDeleteCipher$: jest.fn().mockReturnValue(true), + }, + }, ], }).compileComponents(); diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index 99829e8f086..d30c453a4bd 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -1,6 +1,7 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnInit } from "@angular/core"; +import { Observable } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -8,10 +9,12 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { CollectionId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { AsyncActionsModule, DialogModule, @@ -34,6 +37,11 @@ export interface ViewCipherDialogParams { */ collections?: CollectionView[]; + /** + * Optional collection ID used to know the collection filter selected. + */ + activeCollectionId?: CollectionId; + /** * If true, the edit button will be disabled in the dialog. */ @@ -71,6 +79,8 @@ export class ViewComponent implements OnInit { cipherTypeString: string; organization: Organization; + canDeleteCipher$: Observable; + constructor( @Inject(DIALOG_DATA) public params: ViewCipherDialogParams, private dialogRef: DialogRef, @@ -81,6 +91,7 @@ export class ViewComponent implements OnInit { private cipherService: CipherService, private toastService: ToastService, private organizationService: OrganizationService, + private cipherAuthorizationService: CipherAuthorizationService, ) {} /** @@ -93,6 +104,10 @@ export class ViewComponent implements OnInit { if (this.cipher.organizationId) { this.organization = await this.organizationService.get(this.cipher.organizationId); } + + this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ + this.params.activeCollectionId, + ]); } /** diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index 9cb5542a7b7..7a4697f5af6 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -21,6 +21,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -57,6 +58,7 @@ export class AddEditComponent extends BaseAddEditComponent { datePipe: DatePipe, configService: ConfigService, billingAccountProfileStateService: BillingAccountProfileStateService, + cipherAuthorizationService: CipherAuthorizationService, ) { super( cipherService, @@ -79,6 +81,7 @@ export class AddEditComponent extends BaseAddEditComponent { datePipe, configService, billingAccountProfileStateService, + cipherAuthorizationService, ); } @@ -90,6 +93,7 @@ export class AddEditComponent extends BaseAddEditComponent { } protected async loadCipher() { + this.isAdminConsoleAction = true; // Calling loadCipher first to assess if the cipher is unassigned. If null use apiService getCipherAdmin const firstCipherCheck = await super.loadCipher(); diff --git a/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts b/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts index eb81fa196d5..1e4280626fe 100644 --- a/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts +++ b/apps/web/src/app/vault/org-vault/collection-access-restricted.component.ts @@ -15,7 +15,7 @@ const icon = svgIcon` - {{ "collectionAccessRestricted" | i18n }} + {{ "youDoNotHavePermissions" | i18n }} - - -
- - - {{ "all" | i18n }} - {{ allCount }} - - - - {{ "invited" | i18n }} - {{ invitedCount }} - - - - {{ "needsConfirmation" | i18n }} - {{ acceptedCount }} - - - - -
- - - - {{ "loading" | i18n }} - - -

{{ "noUsersInList" | i18n }}

- - - {{ "providerUsersNeedConfirmed" | i18n }} - - - - - - - - - - - - -
- - - - - {{ u.email }} - {{ - "invited" | i18n - }} - {{ - "needsConfirmation" | i18n - }} - {{ u.name }} - - - - {{ "userUsingTwoStep" | i18n }} - - - {{ "providerAdmin" | i18n }} - {{ "serviceUser" | i18n }} - - -
-
-
- - - - - diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.ts deleted file mode 100644 index 9293f8c6eb7..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { lastValueFrom } from "rxjs"; -import { first } from "rxjs/operators"; - -import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; -import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request"; -import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-confirm.request"; -import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { BasePeopleComponent } from "@bitwarden/web-vault/app/admin-console/common/base.people.component"; -import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component"; -import { BulkStatusComponent } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/bulk-status.component"; - -import { BulkConfirmComponent } from "./bulk/bulk-confirm.component"; -import { BulkRemoveComponent } from "./bulk/bulk-remove.component"; -import { UserAddEditComponent } from "./user-add-edit.component"; - -/** - * @deprecated Please use the {@link MembersComponent} instead. - */ -@Component({ - selector: "provider-people", - templateUrl: "people.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class PeopleComponent - extends BasePeopleComponent - implements OnInit -{ - @ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef; - @ViewChild("groupsTemplate", { read: ViewContainerRef, static: true }) - groupsModalRef: ViewContainerRef; - @ViewChild("bulkStatusTemplate", { read: ViewContainerRef, static: true }) - bulkStatusModalRef: ViewContainerRef; - @ViewChild("bulkConfirmTemplate", { read: ViewContainerRef, static: true }) - bulkConfirmModalRef: ViewContainerRef; - @ViewChild("bulkRemoveTemplate", { read: ViewContainerRef, static: true }) - bulkRemoveModalRef: ViewContainerRef; - - userType = ProviderUserType; - userStatusType = ProviderUserStatusType; - status: ProviderUserStatusType = null; - providerId: string; - accessEvents = false; - - constructor( - apiService: ApiService, - private route: ActivatedRoute, - i18nService: I18nService, - modalService: ModalService, - platformUtilsService: PlatformUtilsService, - cryptoService: CryptoService, - private encryptService: EncryptService, - private router: Router, - searchService: SearchService, - validationService: ValidationService, - logService: LogService, - searchPipe: SearchPipe, - userNamePipe: UserNamePipe, - private providerService: ProviderService, - dialogService: DialogService, - organizationManagementPreferencesService: OrganizationManagementPreferencesService, - private configService: ConfigService, - protected toastService: ToastService, - ) { - super( - apiService, - searchService, - i18nService, - platformUtilsService, - cryptoService, - validationService, - modalService, - logService, - searchPipe, - userNamePipe, - dialogService, - organizationManagementPreferencesService, - toastService, - ); - } - - ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.params.subscribe(async (params) => { - this.providerId = params.providerId; - const provider = await this.providerService.get(this.providerId); - - if (!provider.canManageUsers) { - // 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(["../"], { relativeTo: this.route }); - return; - } - - this.accessEvents = provider.useEvents; - - await this.load(); - - /* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */ - this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - this.searchControl.setValue(qParams.search); - if (qParams.viewEvents != null) { - const user = this.users.filter((u) => u.id === qParams.viewEvents); - if (user.length > 0 && user[0].status === ProviderUserStatusType.Confirmed) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.events(user[0]); - } - } - }); - }); - } - - getUsers(): Promise> { - return this.apiService.getProviderUsers(this.providerId); - } - - deleteUser(id: string): Promise { - return this.apiService.deleteProviderUser(this.providerId, id); - } - - revokeUser(id: string): Promise { - // Not implemented. - return null; - } - - restoreUser(id: string): Promise { - // Not implemented. - return null; - } - - reinviteUser(id: string): Promise { - return this.apiService.postProviderUserReinvite(this.providerId, id); - } - - async confirmUser(user: ProviderUserUserDetailsResponse, publicKey: Uint8Array): Promise { - const providerKey = await this.cryptoService.getProviderKey(this.providerId); - const key = await this.encryptService.rsaEncrypt(providerKey.key, publicKey); - const request = new ProviderUserConfirmRequest(); - request.key = key.encryptedString; - await this.apiService.postProviderUserConfirm(this.providerId, user.id, request); - } - - async edit(user: ProviderUserUserDetailsResponse) { - const [modal] = await this.modalService.openViewRef( - UserAddEditComponent, - this.addEditModalRef, - (comp) => { - comp.name = this.userNamePipe.transform(user); - comp.providerId = this.providerId; - comp.providerUserId = user != null ? user.id : null; - comp.savedUser.subscribe(() => { - modal.close(); - this.load(); - }); - comp.deletedUser.subscribe(() => { - modal.close(); - this.removeUser(user); - }); - }, - ); - } - - async events(user: ProviderUserUserDetailsResponse) { - await openEntityEventsDialog(this.dialogService, { - data: { - name: this.userNamePipe.transform(user), - providerId: this.providerId, - entityId: user.id, - showUser: false, - entity: "user", - }, - }); - } - - async bulkRemove() { - if (this.actionPromise != null) { - return; - } - - const [modal] = await this.modalService.openViewRef( - BulkRemoveComponent, - this.bulkRemoveModalRef, - (comp) => { - comp.providerId = this.providerId; - comp.users = this.getCheckedUsers(); - }, - ); - - await modal.onClosedPromise(); - await this.load(); - } - - async bulkReinvite() { - if (this.actionPromise != null) { - return; - } - - const users = this.getCheckedUsers(); - const filteredUsers = users.filter((u) => u.status === ProviderUserStatusType.Invited); - - if (filteredUsers.length <= 0) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("noSelectedUsersApplicable"), - }); - return; - } - - try { - const request = new ProviderUserBulkRequest(filteredUsers.map((user) => user.id)); - const response = this.apiService.postManyProviderUserReinvite(this.providerId, request); - // 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 - - // Bulk Status component open - const dialogRef = BulkStatusComponent.open(this.dialogService, { - data: { - users: users, - filteredUsers: filteredUsers, - request: response, - successfulMessage: this.i18nService.t("bulkReinviteMessage"), - }, - }); - await lastValueFrom(dialogRef.closed); - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = null; - } - - async bulkConfirm() { - if (this.actionPromise != null) { - return; - } - - const [modal] = await this.modalService.openViewRef( - BulkConfirmComponent, - this.bulkConfirmModalRef, - (comp) => { - comp.providerId = this.providerId; - comp.users = this.getCheckedUsers(); - }, - ); - - await modal.onClosedPromise(); - await this.load(); - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html index 5f9b3f66bc5..0536221cafd 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html @@ -13,7 +13,7 @@ route="manage" *ngIf="showManageTab(provider)" > - + provider.canManageUsers), - ], - data: { - titleId: "people", - }, + { + path: "members", + component: MembersComponent, + canActivate: [ + providerPermissionsGuard((provider: Provider) => provider.canManageUsers), + ], + data: { + titleId: "members", }, - }), + }, { path: "events", component: EventsComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 8ed10a2d6e3..b6c7125c48c 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -24,14 +24,11 @@ import { AddOrganizationComponent } from "./clients/add-organization.component"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; -import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component"; -import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component"; import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component"; import { BulkConfirmDialogComponent } from "./manage/dialogs/bulk-confirm-dialog.component"; import { BulkRemoveDialogComponent } from "./manage/dialogs/bulk-remove-dialog.component"; import { EventsComponent } from "./manage/events.component"; import { MembersComponent } from "./manage/members.component"; -import { PeopleComponent } from "./manage/people.component"; import { UserAddEditComponent } from "./manage/user-add-edit.component"; import { ProvidersLayoutComponent } from "./providers-layout.component"; import { ProvidersRoutingModule } from "./providers-routing.module"; @@ -58,14 +55,11 @@ import { SetupComponent } from "./setup/setup.component"; AcceptProviderComponent, AccountComponent, AddOrganizationComponent, - BulkConfirmComponent, BulkConfirmDialogComponent, - BulkRemoveComponent, BulkRemoveDialogComponent, ClientsComponent, CreateOrganizationComponent, EventsComponent, - PeopleComponent, MembersComponent, SetupComponent, SetupProviderComponent, diff --git a/libs/admin-console/src/common/collections/models/collection.view.ts b/libs/admin-console/src/common/collections/models/collection.view.ts index 3a00ef57f16..037509634b4 100644 --- a/libs/admin-console/src/common/collections/models/collection.view.ts +++ b/libs/admin-console/src/common/collections/models/collection.view.ts @@ -74,7 +74,7 @@ export class CollectionView implements View, ITreeNodeObject { ); } - const canDeleteManagedCollections = !org?.limitCollectionCreationDeletion || org.isAdmin; + const canDeleteManagedCollections = !org?.limitCollectionDeletion || org.isAdmin; // Only use individual permissions, not admin permissions return canDeleteManagedCollections && this.manage; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index cc7af0c0b05..e8d29bd69ba 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -242,6 +242,10 @@ import { } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { + CipherAuthorizationService, + DefaultCipherAuthorizationService, +} from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; @@ -1334,9 +1338,17 @@ const safeProviders: SafeProvider[] = [ SdkClientFactory, EnvironmentService, PlatformUtilsServiceAbstraction, + AccountServiceAbstraction, + KdfConfigServiceAbstraction, + CryptoServiceAbstraction, ApiServiceAbstraction, ], }), + safeProvider({ + provide: CipherAuthorizationService, + useClass: DefaultCipherAuthorizationService, + deps: [CollectionService, OrganizationServiceAbstraction], + }), ]; @NgModule({ diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 49129a868be..44eaec03a68 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -23,7 +23,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CollectionId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; @@ -36,6 +36,7 @@ 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 { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -47,6 +48,7 @@ export class AddEditComponent implements OnInit, OnDestroy { @Input() type: CipherType; @Input() collectionIds: string[]; @Input() organizationId: string = null; + @Input() collectionId: string = null; @Output() onSavedCipher = new EventEmitter(); @Output() onDeletedCipher = new EventEmitter(); @Output() onRestoredCipher = new EventEmitter(); @@ -57,6 +59,8 @@ export class AddEditComponent implements OnInit, OnDestroy { @Output() onGeneratePassword = new EventEmitter(); @Output() onGenerateUsername = new EventEmitter(); + canDeleteCipher$: Observable; + editMode = false; cipher: CipherView; folders$: Observable; @@ -83,6 +87,10 @@ export class AddEditComponent implements OnInit, OnDestroy { reprompt = false; canUseReprompt = true; organization: Organization; + /** + * Flag to determine if the action is being performed from the admin console. + */ + isAdminConsoleAction: boolean = false; protected componentName = ""; protected destroy$ = new Subject(); @@ -118,6 +126,7 @@ export class AddEditComponent implements OnInit, OnDestroy { protected win: Window, protected datePipe: DatePipe, protected configService: ConfigService, + protected cipherAuthorizationService: CipherAuthorizationService, ) { this.typeOptions = [ { name: i18nService.t("typeLogin"), value: CipherType.Login }, @@ -314,6 +323,12 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.reprompt) { this.cipher.login.autofillOnPageLoad = this.autofillOnPageLoadOptions[2].value; } + + this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$( + this.cipher, + [this.collectionId as CollectionId], + this.isAdminConsoleAction, + ); } async submit(): Promise { diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index ac644acf9e4..4c96c10dac3 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -9,7 +9,7 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom, map, Observable } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -28,6 +28,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; +import { CollectionId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -37,6 +38,7 @@ import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -45,12 +47,14 @@ const BroadcasterSubscriptionId = "ViewComponent"; @Directive() export class ViewComponent implements OnDestroy, OnInit { @Input() cipherId: string; + @Input() collectionId: string; @Output() onEditCipher = new EventEmitter(); @Output() onCloneCipher = new EventEmitter(); @Output() onShareCipher = new EventEmitter(); @Output() onDeletedCipher = new EventEmitter(); @Output() onRestoredCipher = new EventEmitter(); + canDeleteCipher$: Observable; cipher: CipherView; showPassword: boolean; showPasswordCount: boolean; @@ -105,6 +109,7 @@ export class ViewComponent implements OnDestroy, OnInit { protected datePipe: DatePipe, protected accountService: AccountService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private cipherAuthorizationService: CipherAuthorizationService, ) {} ngOnInit() { @@ -144,6 +149,9 @@ export class ViewComponent implements OnDestroy, OnInit { ); this.showPremiumRequiredTotp = this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp; + this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ + this.collectionId as CollectionId, + ]); if (this.cipher.folderId) { this.folder = await ( diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index 27446335740..f805da0700a 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -14,17 +14,17 @@ export interface AnonLayoutWrapperData { * If a string is provided, it will be presented as is (ex: Organization name) * If a Translation object (supports placeholders) is provided, it will be translated */ - pageTitle?: string | Translation; + pageTitle?: string | Translation | null; /** * The optional subtitle of the page. * If a string is provided, it will be presented as is (ex: user's email) * If a Translation object (supports placeholders) is provided, it will be translated */ - pageSubtitle?: string | Translation; + pageSubtitle?: string | Translation | null; /** * The optional icon to display on the page. */ - pageIcon?: Icon; + pageIcon?: Icon | null; /** * Optional flag to either show the optional environment selector (false) or just a readonly hostname (true). */ @@ -114,19 +114,23 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { return; } - if (data.pageTitle) { - this.pageTitle = this.handleStringOrTranslation(data.pageTitle); + // Null emissions are used to reset the page data as all fields are optional. + + if (data.pageTitle !== undefined) { + this.pageTitle = + data.pageTitle !== null ? this.handleStringOrTranslation(data.pageTitle) : null; } - if (data.pageSubtitle) { - this.pageSubtitle = this.handleStringOrTranslation(data.pageSubtitle); + if (data.pageSubtitle !== undefined) { + this.pageSubtitle = + data.pageSubtitle !== null ? this.handleStringOrTranslation(data.pageSubtitle) : null; } - if (data.pageIcon) { - this.pageIcon = data.pageIcon; + if (data.pageIcon !== undefined) { + this.pageIcon = data.pageIcon !== null ? data.pageIcon : null; } - if (data.showReadonlyHostname != null) { + if (data.showReadonlyHostname !== undefined) { this.showReadonlyHostname = data.showReadonlyHostname; } diff --git a/libs/auth/src/angular/icons/index.ts b/libs/auth/src/angular/icons/index.ts index cfcad992e34..26e668b7841 100644 --- a/libs/auth/src/angular/icons/index.ts +++ b/libs/auth/src/angular/icons/index.ts @@ -3,3 +3,6 @@ export * from "./bitwarden-shield.icon"; export * from "./lock.icon"; export * from "./user-lock.icon"; export * from "./user-verification-biometrics-fingerprint.icon"; +export * from "./registration-user-add.icon"; +export * from "./registration-lock-alt.icon"; +export * from "./registration-expired-link.icon"; diff --git a/libs/auth/src/angular/icons/registration-check-email.icon.ts b/libs/auth/src/angular/icons/registration-check-email.icon.ts index 1d173ff585f..6f7dd6a2d63 100644 --- a/libs/auth/src/angular/icons/registration-check-email.icon.ts +++ b/libs/auth/src/angular/icons/registration-check-email.icon.ts @@ -1,12 +1,23 @@ import { svgIcon } from "@bitwarden/components"; export const RegistrationCheckEmailIcon = svgIcon` - - - - - - - - -`; + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/registration-expired-link.icon.ts b/libs/auth/src/angular/icons/registration-expired-link.icon.ts index 50bcc53808f..3323c7f0b2b 100644 --- a/libs/auth/src/angular/icons/registration-expired-link.icon.ts +++ b/libs/auth/src/angular/icons/registration-expired-link.icon.ts @@ -1,20 +1,9 @@ import { svgIcon } from "@bitwarden/components"; export const RegistrationExpiredLinkIcon = svgIcon` - - - - - - - - - - + + + `; diff --git a/libs/auth/src/angular/icons/registration-lock-alt.icon.ts b/libs/auth/src/angular/icons/registration-lock-alt.icon.ts new file mode 100644 index 00000000000..511f9710dc6 --- /dev/null +++ b/libs/auth/src/angular/icons/registration-lock-alt.icon.ts @@ -0,0 +1,41 @@ +import { svgIcon } from "@bitwarden/components"; + +export const RegistrationLockAltIcon = svgIcon` + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/registration-user-add.icon.ts b/libs/auth/src/angular/icons/registration-user-add.icon.ts new file mode 100644 index 00000000000..69240cd0298 --- /dev/null +++ b/libs/auth/src/angular/icons/registration-user-add.icon.ts @@ -0,0 +1,24 @@ +import { svgIcon } from "@bitwarden/components"; + +export const RegistrationUserAddIcon = svgIcon` + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts index 5417d617a9a..fe6b9b2c7dc 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts @@ -37,6 +37,14 @@ describe("DefaultRegistrationFinishService", () => { }); }); + describe("getOrgNameFromOrgInvite()", () => { + it("returns null", async () => { + const result = await service.getOrgNameFromOrgInvite(); + + expect(result).toBeNull(); + }); + }); + describe("finishRegistration()", () => { let email: string; let emailVerificationToken: string; diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts index 63b01be9953..6d77c777491 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts @@ -15,6 +15,10 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi protected accountApiService: AccountApiService, ) {} + getOrgNameFromOrgInvite(): Promise { + return null; + } + getMasterPasswordPolicyOptsFromOrgInvite(): Promise { return null; } diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index ef40d95dce9..3a6d26ef939 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router, RouterModule } from "@angular/router"; -import { EMPTY, Subject, from, switchMap, takeUntil, tap } from "rxjs"; +import { Subject, firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; @@ -15,6 +15,7 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid import { ToastService } from "@bitwarden/components"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "../../../common"; +import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service"; import { InputPasswordComponent } from "../../input-password/input-password.component"; import { PasswordInputResult } from "../../input-password/password-input-result"; @@ -60,55 +61,72 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { private accountApiService: AccountApiService, private loginStrategyService: LoginStrategyServiceAbstraction, private logService: LogService, + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, ) {} async ngOnInit() { - this.listenForQueryParamChanges(); - this.masterPasswordPolicyOptions = - await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite(); + const qParams = await firstValueFrom(this.activatedRoute.queryParams); + this.handleQueryParams(qParams); + + if ( + qParams.fromEmail && + qParams.fromEmail === "true" && + this.email && + this.emailVerificationToken + ) { + await this.initEmailVerificationFlow(); + } else { + // Org Invite flow OR registration with email verification disabled Flow + const orgInviteFlow = await this.initOrgInviteFlowIfPresent(); + + if (!orgInviteFlow) { + this.initRegistrationWithEmailVerificationDisabledFlow(); + } + } + + this.loading = false; } - private listenForQueryParamChanges() { - this.activatedRoute.queryParams - .pipe( - tap((qParams: Params) => { - if (qParams.email != null && qParams.email.indexOf("@") > -1) { - this.email = qParams.email; - } + private handleQueryParams(qParams: Params) { + if (qParams.email != null && qParams.email.indexOf("@") > -1) { + this.email = qParams.email; + } - if (qParams.token != null) { - this.emailVerificationToken = qParams.token; - } + if (qParams.token != null) { + this.emailVerificationToken = qParams.token; + } - if (qParams.orgSponsoredFreeFamilyPlanToken != null) { - this.orgSponsoredFreeFamilyPlanToken = qParams.orgSponsoredFreeFamilyPlanToken; - } + if (qParams.orgSponsoredFreeFamilyPlanToken != null) { + this.orgSponsoredFreeFamilyPlanToken = qParams.orgSponsoredFreeFamilyPlanToken; + } - if (qParams.acceptEmergencyAccessInviteToken != null && qParams.emergencyAccessId) { - this.acceptEmergencyAccessInviteToken = qParams.acceptEmergencyAccessInviteToken; - this.emergencyAccessId = qParams.emergencyAccessId; - } - }), - switchMap((qParams: Params) => { - if ( - qParams.fromEmail && - qParams.fromEmail === "true" && - this.email && - this.emailVerificationToken - ) { - return from( - this.registerVerificationEmailClicked(this.email, this.emailVerificationToken), - ); - } else { - // org invite flow - this.loading = false; - return EMPTY; - } - }), + if (qParams.acceptEmergencyAccessInviteToken != null && qParams.emergencyAccessId) { + this.acceptEmergencyAccessInviteToken = qParams.acceptEmergencyAccessInviteToken; + this.emergencyAccessId = qParams.emergencyAccessId; + } + } - takeUntil(this.destroy$), - ) - .subscribe(); + private async initOrgInviteFlowIfPresent(): Promise { + this.masterPasswordPolicyOptions = + await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite(); + + const orgName = await this.registrationFinishService.getOrgNameFromOrgInvite(); + if (orgName) { + // Org invite exists + // Set the page title and subtitle appropriately + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { + key: "joinOrganizationName", + placeholders: [orgName], + }, + pageSubtitle: { + key: "finishJoiningThisOrganizationBySettingAMasterPassword", + }, + }); + return true; + } + + return false; } async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { @@ -145,7 +163,12 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { null, ); - await this.loginStrategyService.logIn(credentials); + const authenticationResult = await this.loginStrategyService.logIn(credentials); + + if (authenticationResult?.requiresTwoFactor) { + await this.router.navigate(["/2fa"]); + return; + } this.toastService.showToast({ variant: "success", @@ -162,9 +185,24 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { this.submitting = false; } + private setDefaultPageTitleAndSubtitle() { + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { + key: "setAStrongPassword", + }, + pageSubtitle: { + key: "finishCreatingYourAccountBySettingAPassword", + }, + }); + } + + private async initEmailVerificationFlow() { + this.setDefaultPageTitleAndSubtitle(); + await this.registerVerificationEmailClicked(this.email, this.emailVerificationToken); + } + private async registerVerificationEmailClicked(email: string, emailVerificationToken: string) { const request = new RegisterVerificationEmailClickedRequest(email, emailVerificationToken); - try { const result = await this.accountApiService.registerVerificationEmailClicked(request); @@ -174,11 +212,9 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { message: this.i18nService.t("emailVerifiedV2"), variant: "success", }); - this.loading = false; } } catch (e) { await this.handleRegisterVerificationEmailClickedError(e); - this.loading = false; } } @@ -204,6 +240,10 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { } } + private initRegistrationWithEmailVerificationDisabledFlow() { + this.setDefaultPageTitleAndSubtitle(); + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts index b585aa78ed6..b7abd381084 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts @@ -3,6 +3,13 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod import { PasswordInputResult } from "../../input-password/password-input-result"; export abstract class RegistrationFinishService { + /** + * Retrieves the organization name from an organization invite if it exists. + * Organization invites can currently only be accepted on the web. + * @returns a promise which resolves to the organization name string or null if no invite exists. + */ + abstract getOrgNameFromOrgInvite(): Promise; + /** * Gets the master password policy options from an organization invite if it exits. * Organization invites can currently only be accepted on the web. @@ -18,7 +25,7 @@ export abstract class RegistrationFinishService { * @param orgSponsoredFreeFamilyPlanToken The optional org sponsored free family plan token. * @param acceptEmergencyAccessInviteToken The optional accept emergency access invite token. * @param emergencyAccessId The optional emergency access id which is required to validate the emergency access invite token. - * Returns a promise which resolves to the captcha bypass token string upon a successful account creation. + * @returns a promise which resolves to the captcha bypass token string upon a successful account creation. */ abstract finishRegistration( email: string, diff --git a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.html b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.html index 5aa6866bbe0..77149902310 100644 --- a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.html +++ b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.html @@ -1,6 +1,4 @@
- -

{{ "alreadyHaveAccount" | i18n }} {{ "logIn" | i18n }}{{ "alreadyHaveAccount" | i18n }} + {{ "logIn" | i18n }} diff --git a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts index 1c2883beb08..f01a8c71bba 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { LinkModule } from "@bitwarden/components"; /** * RegistrationStartSecondaryComponentData @@ -17,7 +18,7 @@ export interface RegistrationStartSecondaryComponentData { standalone: true, selector: "auth-registration-start-secondary", templateUrl: "./registration-start-secondary.component.html", - imports: [CommonModule, JslibModule, RouterModule], + imports: [CommonModule, JslibModule, RouterModule, LinkModule], }) export class RegistrationStartSecondaryComponent implements OnInit { loginRoute: string; diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.html b/libs/auth/src/angular/registration/registration-start/registration-start.component.html index ad30f4a87a6..de3611a5975 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.html +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.html @@ -79,19 +79,6 @@

- - -

- {{ "checkYourEmail" | i18n }} -

-
-
+
diff --git a/libs/tools/generator/components/src/generator.module.ts b/libs/tools/generator/components/src/generator.module.ts index c7dfc60bab2..96622774a3f 100644 --- a/libs/tools/generator/components/src/generator.module.ts +++ b/libs/tools/generator/components/src/generator.module.ts @@ -20,6 +20,7 @@ import { SectionHeaderComponent, SelectModule, ToggleGroupModule, + TypographyModule, } from "@bitwarden/components"; import { createRandomizer, @@ -55,6 +56,7 @@ const RANDOMIZER = new SafeInjectionToken("Randomizer"); SectionHeaderComponent, SelectModule, ToggleGroupModule, + TypographyModule, ], providers: [ safeProvider({ diff --git a/libs/tools/generator/components/src/passphrase-settings.component.html b/libs/tools/generator/components/src/passphrase-settings.component.html index c40df97c69c..2a3f4b5a287 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.html +++ b/libs/tools/generator/components/src/passphrase-settings.component.html @@ -1,4 +1,4 @@ - +
{{ "options" | i18n }}
diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 25e028210cc..82524eba4d8 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -1,3 +1,4 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { BehaviorSubject, skip, takeUntil, Subject } from "rxjs"; @@ -47,6 +48,9 @@ export class PassphraseSettingsComponent implements OnInit, OnDestroy { @Input() showHeader: boolean = true; + /** Removes bottom margin from `bit-section` */ + @Input({ transform: coerceBooleanProperty }) disableMargin = false; + /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, diff --git a/libs/tools/generator/components/src/password-generator.component.html b/libs/tools/generator/components/src/password-generator.component.html index 7ec3a565dd3..b4cf8c6cdb6 100644 --- a/libs/tools/generator/components/src/password-generator.component.html +++ b/libs/tools/generator/components/src/password-generator.component.html @@ -13,7 +13,7 @@
-
+
@@ -32,6 +32,7 @@ class="tw-mt-6" *ngIf="(algorithm$ | async)?.id === 'password'" [userId]="this.userId$ | async" + [disableMargin]="disableMargin" (onUpdated)="generate$.next()" /> diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index bf33c7cfca9..e3f9073cb1e 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -1,3 +1,4 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from "@angular/core"; import { BehaviorSubject, @@ -45,6 +46,9 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy { @Input() userId: UserId | null; + /** Removes bottom margin, passed to downstream components */ + @Input({ transform: coerceBooleanProperty }) disableMargin = false; + /** tracks the currently selected credential type */ protected credentialType$ = new BehaviorSubject(null); diff --git a/libs/tools/generator/components/src/password-settings.component.html b/libs/tools/generator/components/src/password-settings.component.html index be443784da0..9c4fb595392 100644 --- a/libs/tools/generator/components/src/password-settings.component.html +++ b/libs/tools/generator/components/src/password-settings.component.html @@ -1,6 +1,6 @@ - + -
{{ "options" | i18n }}
+

{{ "options" | i18n }}

diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index 9466c81a0f4..2a8bff31c4a 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -1,3 +1,4 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, Input, Output, EventEmitter, Component, OnDestroy } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { BehaviorSubject, takeUntil, Subject, map, filter, tap, debounceTime, skip } from "rxjs"; @@ -55,6 +56,9 @@ export class PasswordSettingsComponent implements OnInit, OnDestroy { @Input() waitMs: number = 100; + /** Removes bottom margin from `bit-section` */ + @Input({ transform: coerceBooleanProperty }) disableMargin = false; + /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, diff --git a/libs/tools/generator/components/src/username-generator.component.html b/libs/tools/generator/components/src/username-generator.component.html index a44637d78e5..e9d7d1c1f8c 100644 --- a/libs/tools/generator/components/src/username-generator.component.html +++ b/libs/tools/generator/components/src/username-generator.component.html @@ -2,7 +2,7 @@
-
+
@@ -17,11 +17,11 @@
- +
{{ "options" | i18n }}
-
+
diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index 767c73c398a..fd1a21cc3e9 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -1,3 +1,4 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { @@ -57,6 +58,9 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy { @Output() readonly onGenerated = new EventEmitter(); + /** Removes bottom margin from internal elements */ + @Input({ transform: coerceBooleanProperty }) disableMargin = false; + /** Tracks the selected generation algorithm */ protected credential = this.formBuilder.group({ type: [null as CredentialAlgorithm], diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html index cc1400f0a6c..98da24b5188 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html @@ -12,39 +12,55 @@

{{ "additionalOptions" | i18n }}

>
- {{ "password" | i18n }} - {{ "newPassword" | i18n }} + {{ "password" | i18n }} + + + + + - - - {{ "sendPasswordDescV2" | i18n }} + {{ "sendPasswordDescV3" | i18n }} - - + + {{ "hideYourEmail" | i18n }} diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index 10099479f13..48ab78465c1 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -7,14 +7,20 @@ import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { + AsyncActionsModule, + ButtonModule, CardComponent, CheckboxModule, + DialogService, FormFieldModule, IconButtonModule, SectionComponent, SectionHeaderComponent, + ToastService, TypographyModule, } from "@bitwarden/components"; import { CredentialGeneratorService, Generators } from "@bitwarden/generator-core"; @@ -27,16 +33,18 @@ import { SendFormContainer } from "../../send-form-container"; templateUrl: "./send-options.component.html", standalone: true, imports: [ - SectionComponent, - SectionHeaderComponent, - TypographyModule, - JslibModule, + AsyncActionsModule, + ButtonModule, CardComponent, - FormFieldModule, - ReactiveFormsModule, - IconButtonModule, CheckboxModule, CommonModule, + FormFieldModule, + IconButtonModule, + JslibModule, + ReactiveFormsModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, ], }) export class SendOptionsComponent implements OnInit { @@ -53,7 +61,7 @@ export class SendOptionsComponent implements OnInit { hideEmail: [false as boolean], }); - get shouldShowNewPassword(): boolean { + get hasPassword(): boolean { return this.originalSendView && this.originalSendView.password !== null; } @@ -61,16 +69,22 @@ export class SendOptionsComponent implements OnInit { return this.config.mode === "edit" && this.sendOptionsForm.value.maxAccessCount !== null; } - get viewsLeft(): number { - return this.sendOptionsForm.value.maxAccessCount - ? this.sendOptionsForm.value.maxAccessCount - this.sendOptionsForm.value.accessCount - : 0; + get viewsLeft() { + return String( + this.sendOptionsForm.value.maxAccessCount + ? this.sendOptionsForm.value.maxAccessCount - this.sendOptionsForm.value.accessCount + : 0, + ); } constructor( private sendFormContainer: SendFormContainer, + private dialogService: DialogService, + private sendApiService: SendApiService, private formBuilder: FormBuilder, private policyService: PolicyService, + private i18nService: I18nService, + private toastService: ToastService, private generatorService: CredentialGeneratorService, ) { this.sendFormContainer.registerChildForm("sendOptionsForm", this.sendOptionsForm); @@ -108,16 +122,49 @@ export class SendOptionsComponent implements OnInit { }); }; + removePassword = async () => { + if (!this.originalSendView || !this.originalSendView.password) { + return; + } + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "removePassword" }, + content: { key: "removePasswordConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return false; + } + + await this.sendApiService.removePassword(this.originalSendView.id); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("removedPassword"), + }); + + this.originalSendView.password = null; + this.sendOptionsForm.patchValue({ + password: null, + }); + this.sendOptionsForm.get("password")?.enable(); + }; + ngOnInit() { if (this.sendFormContainer.originalSendView) { this.sendOptionsForm.patchValue({ maxAccessCount: this.sendFormContainer.originalSendView.maxAccessCount, accessCount: this.sendFormContainer.originalSendView.accessCount, - password: null, + password: this.hasPassword ? "************" : null, // 12 masked characters as a placeholder hideEmail: this.sendFormContainer.originalSendView.hideEmail, notes: this.sendFormContainer.originalSendView.notes, }); } + if (this.hasPassword) { + this.sendOptionsForm.get("password")?.disable(); + } + if (!this.config.areSendsAllowed) { this.sendOptionsForm.disable(); } diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html index d4c253303bd..93db4df3187 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html @@ -6,7 +6,7 @@

{{ "sendDetails" | i18n }}

{{ "name" | i18n }} - + { expect(service.sendTypes.map((c) => c.value)).toEqual([SendType.File, SendType.Text]); }); - it("filters disabled sends", (done) => { - const sends = [{ disabled: true }, { disabled: false }, { disabled: true }] as SendView[]; - service.filterFunction$.pipe(first()).subscribe((filterFunction) => { - expect(filterFunction(sends)).toEqual([sends[1]]); - done(); - }); - - service.filterForm.patchValue({}); - }); - it("resets the filter form", () => { service.filterForm.patchValue({ sendType: SendType.Text }); service.resetFilterForm(); diff --git a/libs/tools/send/send-ui/src/services/send-list-filters.service.ts b/libs/tools/send/send-ui/src/services/send-list-filters.service.ts index 5b2c29329b6..b3a21a38332 100644 --- a/libs/tools/send/send-ui/src/services/send-list-filters.service.ts +++ b/libs/tools/send/send-ui/src/services/send-list-filters.service.ts @@ -44,11 +44,6 @@ export class SendListFiltersService { map( (filters) => (sends: SendView[]) => sends.filter((send) => { - // do not show disabled sends - if (send.disabled) { - return false; - } - if (filters.sendType !== null && send.type !== filters.sendType) { return false; } diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts index fdb306ff761..d259566cc57 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts @@ -58,6 +58,13 @@ describe("UriOptionComponent", () => { expect(component["uriMatchOptions"][0].label).toBe("default"); }); + it("should update the default uri match strategy label when it is domain", () => { + component.defaultMatchDetection = UriMatchStrategy.Domain; + fixture.detectChanges(); + + expect(component["uriMatchOptions"][0].label).toBe("defaultLabel baseDomain"); + }); + it("should update the default uri match strategy label", () => { component.defaultMatchDetection = UriMatchStrategy.Exact; fixture.detectChanges(); diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts index 82870befa12..4af80ed464c 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts @@ -84,7 +84,7 @@ export class UriOptionComponent implements ControlValueAccessor { @Input({ required: true }) set defaultMatchDetection(value: UriMatchStrategySetting) { // The default selection has a value of `null` avoid showing "Default (Default)" - if (!value) { + if (value === null) { return; } 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 181ca50da8a..445908679c3 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 @@ -1,8 +1,10 @@ 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 db6e9ae106b..79fac29d4d9 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 @@ -1,3 +1,4 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; @@ -21,6 +22,9 @@ export class CipherFormGeneratorComponent { @Input({ required: true }) type: "password" | "username"; + /** Removes bottom margin of internal sections */ + @Input({ transform: coerceBooleanProperty }) disableMargin = false; + /** * Emits an event when a new value is generated. */ diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html index d48666ad83a..35b0428d125 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html @@ -27,7 +27,7 @@

{{ "itemHistory" | i18n }}

diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.html b/libs/vault/src/components/password-history-view/password-history-view.component.html new file mode 100644 index 00000000000..44b7fea5f75 --- /dev/null +++ b/libs/vault/src/components/password-history-view/password-history-view.component.html @@ -0,0 +1,28 @@ +
+ +
+ +
{{ h.lastUsedDate | date: "medium" }}
+
+ + + + + +
+
+
+

{{ "noPasswordsInList" | i18n }}

+
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 new file mode 100644 index 00000000000..8772a245821 --- /dev/null +++ b/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts @@ -0,0 +1,97 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { BehaviorSubject } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { ColorPasswordModule, ItemModule, ToastService } from "@bitwarden/components"; +import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component"; + +import { PasswordHistoryViewComponent } from "./password-history-view.component"; + +describe("PasswordHistoryViewComponent", () => { + let component: PasswordHistoryViewComponent; + let fixture: ComponentFixture; + + const mockCipher = { + id: "122-333-444", + type: CipherType.Login, + organizationId: "222-444-555", + } as CipherView; + + const copyToClipboard = jest.fn(); + const showToast = jest.fn(); + const activeAccount$ = new BehaviorSubject<{ id: string }>({ id: "666-444-444" }); + const mockCipherService = { + get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }), + getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), + }; + + beforeEach(async () => { + mockCipherService.get.mockClear(); + mockCipherService.getKeyForCipherKeyDecryption.mockClear(); + copyToClipboard.mockClear(); + showToast.mockClear(); + + await TestBed.configureTestingModule({ + imports: [ItemModule, ColorPasswordModule, JslibModule], + providers: [ + { provide: WINDOW, useValue: window }, + { provide: CipherService, useValue: mockCipherService }, + { provide: PlatformUtilsService, useValue: { copyToClipboard } }, + { provide: AccountService, useValue: { activeAccount$ } }, + { provide: ToastService, useValue: { showToast } }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(PasswordHistoryViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("renders no history text when history does not exist", () => { + expect(fixture.debugElement.nativeElement.textContent).toBe("noPasswordsInList"); + }); + + describe("history", () => { + const password1 = { password: "bad-password-1", lastUsedDate: new Date("09/13/2004") }; + const password2 = { password: "bad-password-2", lastUsedDate: new Date("02/01/2004") }; + + beforeEach(async () => { + mockCipher.passwordHistory = [password1, password2]; + + mockCipherService.get.mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }); + await component.ngOnInit(); + fixture.detectChanges(); + }); + + it("renders all passwords", () => { + const passwords = fixture.debugElement.queryAll(By.directive(ColorPasswordComponent)); + + expect(passwords.map((password) => password.componentInstance.password)).toEqual([ + "bad-password-1", + "bad-password-2", + ]); + }); + + it("copies a password", () => { + const copyButton = fixture.debugElement.query(By.css("button")); + + copyButton.nativeElement.click(); + + expect(copyToClipboard).toHaveBeenCalledWith("bad-password-1", { window: window }); + expect(showToast).toHaveBeenCalledWith({ + message: "passwordCopied", + title: "", + variant: "info", + }); + }); + }); +}); diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.ts b/libs/vault/src/components/password-history-view/password-history-view.component.ts new file mode 100644 index 00000000000..5e858af7275 --- /dev/null +++ b/libs/vault/src/components/password-history-view/password-history-view.component.ts @@ -0,0 +1,77 @@ +import { CommonModule } from "@angular/common"; +import { OnInit, Inject, Component, Input } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view"; +import { + ToastService, + ItemModule, + ColorPasswordModule, + IconButtonModule, +} from "@bitwarden/components"; + +@Component({ + selector: "vault-password-history-view", + templateUrl: "./password-history-view.component.html", + standalone: true, + imports: [CommonModule, ItemModule, ColorPasswordModule, IconButtonModule, JslibModule], +}) +export class PasswordHistoryViewComponent implements OnInit { + /** + * The ID of the cipher to display the password history for. + */ + @Input({ required: true }) cipherId: CipherId; + + /** The password history for the cipher. */ + history: PasswordHistoryView[] = []; + + constructor( + @Inject(WINDOW) private win: Window, + protected cipherService: CipherService, + protected platformUtilsService: PlatformUtilsService, + protected i18nService: I18nService, + protected accountService: AccountService, + protected toastService: ToastService, + ) {} + + async ngOnInit() { + await this.init(); + } + + /** Copies a password to the clipboard. */ + copy(password: string) { + const copyOptions = this.win != null ? { window: this.win } : undefined; + this.platformUtilsService.copyToClipboard(password, copyOptions); + this.toastService.showToast({ + variant: "info", + title: "", + message: this.i18nService.t("passwordCopied"), + }); + } + + /** Retrieve the password history for the given cipher */ + protected async init() { + const cipher = await this.cipherService.get(this.cipherId); + const activeAccount = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)), + ); + + if (!activeAccount?.id) { + throw new Error("Active account is not available."); + } + + const activeUserId = activeAccount.id as UserId; + const decCipher = await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + + this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; + } +} diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index d5841c7db06..f6a95281f81 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -12,5 +12,6 @@ export { } from "./components/assign-collections.component"; export { DownloadAttachmentComponent } from "./components/download-attachment/download-attachment.component"; +export { PasswordHistoryViewComponent } from "./components/password-history-view/password-history-view.component"; export * as VaultIcons from "./icons"; diff --git a/package-lock.json b/package-lock.json index f79ed7089f1..3061b1d507d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,10 +24,10 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.3", + "@bitwarden/sdk-internal": "0.1.6", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", - "@koa/router": "12.0.1", + "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "11.2.0", @@ -47,7 +47,7 @@ "jquery": "3.7.1", "jsdom": "25.0.1", "jszip": "3.10.1", - "koa": "2.15.0", + "koa": "2.15.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", @@ -58,7 +58,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.1", "nord": "0.2.1", - "oidc-client-ts": "2.4.0", + "oidc-client-ts": "2.4.1", "open": "8.4.2", "papaparse": "5.4.1", "patch-package": "8.0.0", @@ -68,7 +68,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.51", + "tldts": "6.1.52", "utf-8-validate": "6.0.4", "zone.js": "0.13.3", "zxcvbn": "4.4.2" @@ -103,7 +103,7 @@ "@types/jest": "29.5.12", "@types/jquery": "3.5.30", "@types/jsdom": "21.1.7", - "@types/koa": "2.14.0", + "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", "@types/koa__router": "12.0.4", "@types/koa-bodyparser": "4.3.7", @@ -114,7 +114,7 @@ "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", - "@types/papaparse": "5.3.14", + "@types/papaparse": "5.3.15", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", @@ -146,7 +146,6 @@ "eslint-plugin-storybook": "0.8.0", "eslint-plugin-tailwindcss": "3.17.4", "gulp": "4.0.2", - "gulp-filter": "9.0.1", "gulp-if": "3.0.0", "gulp-json-editor": "2.6.0", "gulp-replace": "1.1.4", @@ -173,7 +172,7 @@ "sass-loader": "16.0.1", "storybook": "8.2.9", "style-loader": "3.3.4", - "tailwindcss": "3.4.11", + "tailwindcss": "3.4.14", "ts-jest": "29.2.2", "ts-loader": "9.5.1", "tsconfig-paths-webpack-plugin": "4.1.0", @@ -202,7 +201,7 @@ "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "3.0.2", - "@koa/router": "12.0.1", + "@koa/router": "13.1.0", "argon2": "0.40.1", "big-integer": "1.6.52", "browser-hrtime": "1.1.8", @@ -213,7 +212,7 @@ "inquirer": "8.2.6", "jsdom": "25.0.1", "jszip": "3.10.1", - "koa": "2.15.0", + "koa": "2.15.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", @@ -225,7 +224,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.51", + "tldts": "6.1.52", "zxcvbn": "4.4.2" }, "bin": { @@ -234,7 +233,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.10.0", + "version": "2024.10.2", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -248,7 +247,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2024.10.2" + "version": "2024.10.3" }, "libs/admin-console": { "name": "@bitwarden/admin-console", @@ -4696,10 +4695,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.3.tgz", - "integrity": "sha512-zk9DyYMjylVLdljeLn3OLBcD939Hg/qMNJ2FxbyjiSKtcOcgglXgYmbcS01NRFFfM9REbn+j+2fWbQo6N+8SHw==", - "license": "SEE LICENSE IN LICENSE" + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.6.tgz", + "integrity": "sha512-YUOOcXnK004mAwE+vfy7AgeLYCtTyafYaXEWED3PNRaSun/a5elrAD//h2yuF9u8Dn5jg1VDkssMPpuG9+2VxA==" }, "node_modules/@bitwarden/vault": { "resolved": "libs/vault", @@ -7021,20 +7019,17 @@ } }, "node_modules/@koa/router": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.1.tgz", - "integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==", - "deprecated": "Use v12.0.2 or higher to fix the vulnerability issue", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", + "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", "license": "MIT", "dependencies": { - "debug": "^4.3.4", "http-errors": "^2.0.0", "koa-compose": "^4.1.0", - "methods": "^1.1.2", - "path-to-regexp": "^6.2.1" + "path-to-regexp": "^6.3.0" }, "engines": { - "node": ">= 12" + "node": ">= 18" } }, "node_modules/@leichtgewicht/ip-codec": { @@ -9524,9 +9519,9 @@ } }, "node_modules/@types/koa": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.14.0.tgz", - "integrity": "sha512-DTDUyznHGNHAl+wd1n0z1jxNajduyTh8R53xoewuerdBzGo6Ogj6F2299BFtrexJw4NtgjsI5SMPCmV9gZwGXA==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", "dev": true, "license": "MIT", "dependencies": { @@ -9702,9 +9697,9 @@ } }, "node_modules/@types/papaparse": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.14.tgz", - "integrity": "sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==", + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz", + "integrity": "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==", "dev": true, "license": "MIT", "dependencies": { @@ -12150,19 +12145,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-differ": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", - "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", @@ -20888,47 +20870,6 @@ "object.assign": "^4.1.0" } }, - "node_modules/gulp-filter": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-9.0.1.tgz", - "integrity": "sha512-knVYL8h9bfYIeft3VokVTkuaWJkQJMrFCS3yVjZQC6BGg+1dZFoeUY++B9D2X4eFpeNTx9StWK0qnDby3NO3PA==", - "dev": true, - "license": "MIT", - "dependencies": { - "multimatch": "^7.0.0", - "plugin-error": "^2.0.1", - "slash": "^5.1.0", - "streamfilter": "^3.0.0", - "to-absolute-glob": "^3.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "gulp": ">=4" - }, - "peerDependenciesMeta": { - "gulp": { - "optional": true - } - } - }, - "node_modules/gulp-filter/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gulp-if": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", @@ -25243,9 +25184,9 @@ } }, "node_modules/koa": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.0.tgz", - "integrity": "sha512-KEL/vU1knsoUvfP4MC4/GthpQrY/p6dzwaaGI6Rt4NQuFqkw3qrvsdYF5pz3wOfi7IGTvMPHC9aZIcUKYFNxsw==", + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", + "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", "license": "MIT", "dependencies": { "accepts": "^1.3.5", @@ -27277,6 +27218,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -28425,37 +28367,6 @@ "multicast-dns": "cli.js" } }, - "node_modules/multimatch": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-7.0.0.tgz", - "integrity": "sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-differ": "^4.0.0", - "array-union": "^3.0.1", - "minimatch": "^9.0.3" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/multimatch/node_modules/array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/multistream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", @@ -29879,9 +29790,9 @@ "license": "MIT" }, "node_modules/oidc-client-ts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz", - "integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.1.tgz", + "integrity": "sha512-IxlGMsbkZPsHJGCliWT3LxjUcYzmiN21656n/Zt2jDncZlBFc//cd8WqFF0Lt681UT3AImM57E6d4N53ziTCYA==", "license": "Apache-2.0", "dependencies": { "crypto-js": "^4.2.0", @@ -35370,19 +35281,6 @@ "dev": true, "license": "MIT" }, - "node_modules/streamfilter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", - "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^3.0.6" - }, - "engines": { - "node": ">=8.12.0" - } - }, "node_modules/streaming-json-stringify": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/streaming-json-stringify/-/streaming-json-stringify-3.1.0.tgz", @@ -35777,9 +35675,9 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz", - "integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==", + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", + "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", "dev": true, "license": "MIT", "dependencies": { @@ -36519,21 +36417,21 @@ } }, "node_modules/tldts": { - "version": "6.1.51", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.51.tgz", - "integrity": "sha512-33lfQoL0JsDogIbZ8fgRyvv77GnRtwkNE/MOKocwUgPO1WrSfsq7+vQRKxRQZai5zd+zg97Iv9fpFQSzHyWdLA==", + "version": "6.1.52", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.52.tgz", + "integrity": "sha512-fgrDJXDjbAverY6XnIt0lNfv8A0cf7maTEaZxNykLGsLG7XP+5xhjBTrt/ieAsFjAlZ+G5nmXomLcZDkxXnDzw==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.51" + "tldts-core": "^6.1.52" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.51", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.51.tgz", - "integrity": "sha512-bu9oCYYWC1iRjx+3UnAjqCsfrWNZV1ghNQf49b3w5xE8J/tNShHTzp5syWJfwGH+pxUgTTLUnzHnfuydW7wmbg==", + "version": "6.1.52", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.52.tgz", + "integrity": "sha512-j4OxQI5rc1Ve/4m/9o2WhWSC4jGc4uVbCINdOEJRAraCi0YqTqgMcxUx7DbmuP0G3PCixoof/RZB0Q5Kh9tagw==", "license": "MIT" }, "node_modules/tmp": { @@ -36576,20 +36474,6 @@ "license": "BSD-3-Clause", "peer": true }, - "node_modules/to-absolute-glob": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-3.0.0.tgz", - "integrity": "sha512-loO/XEWTRqpfcpI7+Jr2RR2Umaaozx1t6OSVWtMi0oy5F/Fxg3IC+D/TToDnxyAGs7uZBGT/6XmyDUxgsObJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/package.json b/package.json index 9414ff67b8c..75e91f64936 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "storybook": "ng run components:storybook", "build-storybook": "ng run components:build-storybook", "build-storybook:ci": "ng run components:build-storybook --webpack-stats-json", - "postinstall": "patch-package" + "postinstall": "patch-package && rimraf ./node_modules/@types/glob && rimraf ./node_modules/@types/minimatch" }, "workspaces": [ "apps/*", @@ -64,7 +64,7 @@ "@types/jest": "29.5.12", "@types/jquery": "3.5.30", "@types/jsdom": "21.1.7", - "@types/koa": "2.14.0", + "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", "@types/koa__router": "12.0.4", "@types/koa-bodyparser": "4.3.7", @@ -75,7 +75,7 @@ "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", - "@types/papaparse": "5.3.14", + "@types/papaparse": "5.3.15", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", @@ -107,7 +107,6 @@ "eslint-plugin-storybook": "0.8.0", "eslint-plugin-tailwindcss": "3.17.4", "gulp": "4.0.2", - "gulp-filter": "9.0.1", "gulp-if": "3.0.0", "gulp-json-editor": "2.6.0", "gulp-replace": "1.1.4", @@ -134,7 +133,7 @@ "sass-loader": "16.0.1", "storybook": "8.2.9", "style-loader": "3.3.4", - "tailwindcss": "3.4.11", + "tailwindcss": "3.4.14", "ts-jest": "29.2.2", "ts-loader": "9.5.1", "tsconfig-paths-webpack-plugin": "4.1.0", @@ -158,10 +157,10 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.3", + "@bitwarden/sdk-internal": "0.1.6", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", - "@koa/router": "12.0.1", + "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "11.2.0", @@ -181,7 +180,7 @@ "jquery": "3.7.1", "jsdom": "25.0.1", "jszip": "3.10.1", - "koa": "2.15.0", + "koa": "2.15.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", @@ -192,7 +191,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.1", "nord": "0.2.1", - "oidc-client-ts": "2.4.0", + "oidc-client-ts": "2.4.1", "open": "8.4.2", "papaparse": "5.4.1", "patch-package": "8.0.0", @@ -202,7 +201,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.51", + "tldts": "6.1.52", "utf-8-validate": "6.0.4", "zone.js": "0.13.3", "zxcvbn": "4.4.2" @@ -212,11 +211,7 @@ "@storybook/angular": { "zone.js": "$zone.js" }, - "replacestream": "4.0.3", - "@types/minimatch": "3.0.5", - "@electron/asar": { - "@types/glob": "7.1.3" - } + "replacestream": "4.0.3" }, "lint-staged": { "*": "prettier --cache --ignore-unknown --write",