diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index ad2ac539715..1f1b9936bf6 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -65,15 +65,15 @@ jobs: strategy: matrix: os: - [ - { base: "linux", distro: "ubuntu-22.04" }, - { base: "mac", distro: "macos-13" } - ] + [ + { base: "linux", distro: "ubuntu-22.04" }, + { base: "mac", distro: "macos-13" } + ] license_type: - [ - { build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" }, - { build_prefix: "bit", artifact_prefix: "", readable: "commercial license"} - ] + [ + { build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" }, + { build_prefix: "bit", artifact_prefix: "", readable: "commercial license" } + ] runs-on: ${{ matrix.os.distro }} needs: - setup @@ -148,10 +148,10 @@ jobs: strategy: matrix: license_type: - [ - { build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" }, - { build_prefix: "bit", artifact_prefix: "", readable: "commercial license"} - ] + [ + { build_prefix: "oss", artifact_prefix: "-oss", readable: "open source license" }, + { build_prefix: "bit", artifact_prefix: "", readable: "commercial license" } + ] runs-on: windows-2022 needs: - setup @@ -241,7 +241,7 @@ jobs: - name: Package Chocolatey shell: pwsh - if: ${{ matrix.license_type.build_prefix }} == 'bit' + if: ${{ matrix.license_type.build_prefix == 'bit' }} run: | Copy-Item -Path stores/chocolatey -Destination dist/chocolatey -Recurse Copy-Item dist/${{ matrix.license_type.build_prefix }}/windows/bw.exe -Destination dist/chocolatey/tools diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index f422c3560e6..9ae2db7244b 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -1,3 +1,4 @@ +--- name: Chromatic on: @@ -13,7 +14,7 @@ jobs: check-run: name: Check PR run uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - + chromatic: name: Chromatic runs-on: ubuntu-22.04 diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index b6c2e276461..1f5df5a66c0 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -59,4 +59,3 @@ jobs: working_directory: apps/${{ matrix.app_name }} gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }} gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }} - diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index 5aa92c4dd8a..27475709b63 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -7,7 +7,7 @@ on: inputs: environment: description: 'Environment' - default: 'QA' + default: 'USQA' type: choice options: - USQA diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 3f9eb7b2e42..09b6b53e584 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -35,7 +35,6 @@ on: default: true type: boolean - defaults: run: working-directory: apps/cli @@ -46,29 +45,42 @@ jobs: runs-on: ubuntu-22.04 outputs: release-version: ${{ steps.version-output.outputs.version }} - deployment-id: ${{ steps.deployment.outputs.deployment-id }} + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + defaults: + run: + working-directory: . steps: + - name: Branch check + if: ${{ inputs.publish_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-cli" ]]; then + echo "===================================" + echo "[!] Can only publish from the 'rc' or 'hotfix-rc-cli' branches" + echo "===================================" + exit 1 + fi + - name: Version output id: version-output run: | - if [[ "${{ github.event.inputs.version }}" == "latest" || "${{ github.event.inputs.version }}" == "" ]]; then + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then VERSION=$(curl "https://api.github.com/repos/bitwarden/clients/releases" | jq -c '.[] | select(.tag_name | contains("cli")) | .tag_name' | head -1 | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') echo "Latest Released Version: $VERSION" - echo "::set-output name=version::$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT else - echo "Release Version: ${{ github.event.inputs.version }}" - echo "::set-output name=version::${{ github.event.inputs.version }}" + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT fi - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' environment: 'CLI - Production' - description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ github.ref_name }}' + description: 'Deployment ${{ steps.version-output.outputs.version }} from branch ${{ github.ref_name }}' task: release snap: @@ -98,10 +110,10 @@ jobs: uses: samuelmeuli/action-snapcraft@d33c176a9b784876d966f80fb1b461808edc0641 # v2.1.1 - name: Download artifacts - run: wget https://github.com/bitwarden/clients/releases/cli-v${{ env._PKG_VERSION }}/download/bw_${{ env._PKG_VERSION }}_amd64.snap + run: wget https://github.com/bitwarden/clients/releases/download/cli-v${{ env._PKG_VERSION }}/bw_${{ env._PKG_VERSION }}_amd64.snap - name: Publish Snap & logout - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | @@ -137,18 +149,16 @@ jobs: CHOCO_API_KEY: ${{ steps.retrieve-secrets.outputs.cli-choco-api-key }} - name: Make dist dir - shell: pwsh run: New-Item -ItemType directory -Path ./dist - name: Download artifacts - run: wget https://github.com/bitwarden/clients/releases/cli-v${{ env._PKG_VERSION }}/download/bitwarden-cli.${{ env._PKG_VERSION }}.nupkg + run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/cli-v${{ env._PKG_VERSION }}/bitwarden-cli.${{ env._PKG_VERSION }}.nupkg" -OutFile bitwarden-cli.${{ env._PKG_VERSION }}.nupkg + working-directory: apps/cli/dist - name: Push to Chocolatey - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} - shell: pwsh - run: | - cd dist - choco push --source=https://push.chocolatey.org/ + if: ${{ inputs.publish_type != 'Dry Run' }} + run: choco push --source=https://push.chocolatey.org/ + working-directory: apps/cli/dist npm: name: Publish NPM @@ -174,7 +184,7 @@ jobs: secrets: "npm-api-key" - name: Download artifacts - run: wget https://github.com/bitwarden/clients/releases/cli-v${{ env._PKG_VERSION }}/download/bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip + run: wget https://github.com/bitwarden/clients/releases/download/cli-v${{ env._PKG_VERSION }}/bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip - name: Setup NPM run: | @@ -187,7 +197,7 @@ jobs: run: npm install -g husky - name: Publish NPM - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} run: npm publish --access public --regsitry=https://registry.npmjs.org/ --userconfig=./.npmrc update-deployment: @@ -198,14 +208,14 @@ jobs: - npm - snap - choco - if: ${{ always() && github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ always() && inputs.publish_type != 'Dry Run' }} steps: - name: Check if any job failed if: contains(needs.*.result, 'failure') run: exit 1 - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + if: ${{ inputs.publish_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' @@ -213,9 +223,9 @@ jobs: deployment-id: ${{ needs.setup.outputs.deployment-id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + if: ${{ inputs.publish_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment-id }} \ No newline at end of file + deployment-id: ${{ needs.setup.outputs.deployment-id }} diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 2c4e467bc2a..c03697fc802 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -15,7 +15,7 @@ on: - Republish - Dry Run version: - description: 'Version to publish (default: latest cli release)' + description: 'Version to publish (default: latest desktop release)' required: true type: string default: latest @@ -35,10 +35,6 @@ on: default: true type: boolean -defaults: - run: - shell: bash - jobs: setup: name: Setup @@ -49,25 +45,35 @@ jobs: tag-name: ${{ steps.version.outputs.tag_name }} deployment-id: ${{ steps.deployment.outputs.deployment_id }} steps: + - name: Branch check + if: ${{ inputs.publish_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-desktop" ]]; then + echo "===================================" + echo "[!] Can only publish from the 'rc' or 'hotfix-rc-desktop' branches" + echo "===================================" + exit 1 + fi + - name: Check Publish Version id: version run: | - if [[ "${{ github.event.inputs.version }}" == "latest" || "${{ github.event.inputs.version }}" == "" ]]; then + if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/clients/releases" | jq -c '.[] | select(.tag_name | contains("desktop")) | .tag_name' | head -1 | cut -d '"' -f 2) VERSION=$(echo $TAG_NAME | sed "s/desktop-v//") echo "Latest Released Version: $VERSION" - echo "::set-output name=version::$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Tag name: $TAG_NAME" - echo "::set-output name=tag_name::$TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT else - echo "Release Version: ${{ github.event.inputs.version }}" - echo "::set-output name=version::${{ github.event.inputs.version }}" + echo "Release Version: ${{ inputs.version }}" + echo "version=${{ inputs.version }}" - $TAG_NAME="desktop-v${{ github.event.inputs.version }}" + $TAG_NAME="desktop-v${{ inputs.version }}" echo "Tag name: $TAG_NAME" - echo "::set-output name=tag_name::$TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT fi - name: Get Version Channel @@ -88,7 +94,7 @@ jobs: esac - name: Create GitHub deployment - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: @@ -101,6 +107,7 @@ jobs: electron-blob: name: Electron blob publish runs-on: ubuntu-22.04 + needs: setup env: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} @@ -118,23 +125,19 @@ jobs: secrets: "aws-electron-access-id, aws-electron-access-key, aws-electron-bucket-name" - - - name: Download all artifacts - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-desktop.yml - workflow_conclusion: success - branch: ${{ github.ref_name }} - path: apps/desktop/artifacts + + - name: Create artifacts directory + run: mkdir -p apps/desktop/artifacts - name: Download artifacts + env: + GH_TOKEN: ${{ github.token }} working-directory: apps/desktop/artifacts - run: gh release download ${{ env._RELEASE_TAG }} -R bitwarden/desktop + run: gh release download ${{ env._RELEASE_TAG }} -R bitwarden/clients - name: Set staged rollout percentage env: - RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} + RELEASE_CHANNEL: ${{ needs.setup.outputs.release-channel }} ROLLOUT_PCT: ${{ inputs.rollout_percentage }} run: | echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml @@ -142,7 +145,7 @@ jobs: echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml - name: Publish artifacts to S3 - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} env: AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }} AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }} @@ -156,26 +159,26 @@ jobs: --quiet - name: Update deployment status to Success - if: ${{ github.event.inputs.publish_type != 'Dry Run' && success() }} + if: ${{ inputs.publish_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment-id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.publish_type != 'Dry Run' && failure() }} + if: ${{ inputs.publish_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ steps.deployment.outputs.deployment_id }} + deployment-id: ${{ needs.setup.outputs.deployment-id }} snap: name: Deploy Snap runs-on: ubuntu-22.04 needs: setup - if: ${{ github.event.inputs.snap_publish == 'true' }} + if: inputs.snap_publish env: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} @@ -204,10 +207,10 @@ jobs: - name: Download artifacts working-directory: apps/desktop/dist - run: wget https://github.com/bitwarden/clients/releases/${{ env._RELEASE_TAG }}/download/bitwarden_${{ env._PKG_VERSION }}_amd64.snap + run: wget https://github.com/bitwarden/clients/releases/download/${{ env._RELEASE_TAG }}/bitwarden_${{ env._PKG_VERSION }}_amd64.snap - name: Deploy to Snap Store - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | @@ -219,7 +222,7 @@ jobs: name: Deploy Choco runs-on: windows-2022 needs: setup - if: ${{ github.event.inputs.choco_publish == 'true' }} + if: inputs.choco_publish env: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} @@ -245,23 +248,20 @@ jobs: secrets: "cli-choco-api-key" - name: Setup Chocolatey - shell: pwsh run: choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ env: CHOCO_API_KEY: ${{ steps.retrieve-secrets.outputs.cli-choco-api-key }} - name: Make dist dir - shell: pwsh run: New-Item -ItemType directory -Path ./dist working-directory: apps/desktop - name: Download artifacts working-directory: apps/desktop/dist - run: wget https://github.com/bitwarden/clients/releases/${{ env._RELEASE_TAG }}/download/bitwarden.${{ env._PKG_VERSION }}.nupkg + run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/${{ env._RELEASE_TAG }}/bitwarden.${{ env._PKG_VERSION }}.nupkg" -OutFile bitwarden.${{ env._PKG_VERSION }}.nupkg - name: Push to Chocolatey - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} - shell: pwsh + if: ${{ inputs.publish_type != 'Dry Run' }} run: choco push --source=https://push.chocolatey.org/ working-directory: apps/desktop/dist @@ -273,14 +273,14 @@ jobs: - electron-blob - snap - choco - if: ${{ always() && github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ always() && inputs.publish_type != 'Dry Run' }} steps: - name: Check if any job failed if: contains(needs.*.result, 'failure') run: exit 1 - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + if: ${{ inputs.publish_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' @@ -288,7 +288,7 @@ jobs: deployment-id: ${{ needs.setup.outputs.deployment-id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + if: ${{ inputs.publish_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index 733e3945e5f..b7ea8498593 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -8,10 +8,10 @@ on: publish_type: description: 'Publish Options' required: true - default: 'Initial Publish' + default: 'Initial Release' type: choice options: - - Initial Publish + - Initial Release - Redeploy - Dry Run @@ -30,11 +30,11 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Branch check - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} run: | if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-web" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc-web' branches" + echo "[!] Can only publish from the 'rc' or 'hotfix-rc-web' branches" echo "===================================" exit 1 fi @@ -43,7 +43,7 @@ jobs: id: version uses: bitwarden/gh-actions/release-version-check@main with: - release-type: ${{ github.event.inputs.publish_type }} + release-type: ${{ inputs.publish_type }} project-type: ts file: apps/web/package.json monorepo: true @@ -56,7 +56,7 @@ jobs: env: _BRANCH_NAME: ${{ github.ref_name }} _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} - _RELEASE_OPTION: ${{ github.event.inputs.publish_type }} + _RELEASE_OPTION: ${{ inputs.publish_type }} steps: - name: Print environment run: | @@ -79,7 +79,7 @@ jobs: run: az acr login -n bitwardenprod - name: Create GitHub deployment - if: ${{ github.event.inputs.publish_type != 'Dry Run' }} + if: ${{ inputs.publish_type != 'Dry Run' }} uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: @@ -92,7 +92,7 @@ jobs: - name: Pull branch image run: | - if [[ "${{ github.event.inputs.publish_type }}" == "Dry Run" ]]; then + if [[ "${{ inputs.publish_type }}" == "Dry Run" ]]; then docker pull $_AZ_REGISTRY/web:latest else docker pull $_AZ_REGISTRY/web:$_BRANCH_NAME @@ -100,7 +100,7 @@ jobs: - name: Tag version run: | - if [[ "${{ github.event.inputs.publish_type }}" == "Dry Run" ]]; then + if [[ "${{ inputs.publish_type }}" == "Dry Run" ]]; then docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web:dryrun docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web-sh:dryrun else @@ -112,7 +112,7 @@ jobs: - name: Push version run: | - if [[ "${{ github.event.inputs.publish_type }}" == "Dry Run" ]]; then + if [[ "${{ inputs.publish_type }}" == "Dry Run" ]]; then docker push $_AZ_REGISTRY/web:dryrun docker push $_AZ_REGISTRY/web-sh:dryrun else @@ -123,7 +123,7 @@ jobs: fi - name: Update deployment status to Success - if: ${{ github.event.inputs.publish_type != 'Dry Run' && success() }} + if: ${{ inputs.publish_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' @@ -132,7 +132,7 @@ jobs: deployment-id: ${{ steps.deployment.outputs.deployment_id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.publish_type != 'Dry Run' && failure() }} + if: ${{ inputs.publish_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index fe402e7a8f1..ddcdb4e904f 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-cli" ]]; then echo "===================================" @@ -43,7 +43,7 @@ jobs: id: version uses: bitwarden/gh-actions/release-version-check@main with: - release-type: ${{ github.event.inputs.release_type }} + release-type: ${{ inputs.release_type }} project-type: ts file: apps/cli/package.json monorepo: true @@ -55,7 +55,7 @@ jobs: needs: setup steps: - name: Download all Release artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-cli.yml @@ -64,7 +64,7 @@ jobs: branch: ${{ github.ref_name }} - name: Dry Run - Download all artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} + if: ${{ inputs.release_type == 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-cli.yml @@ -73,10 +73,10 @@ jobs: branch: main - name: Create release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 env: - PKG_VERSION: ${{ steps.version.outputs.version }} + PKG_VERSION: ${{ needs.setup.outputs.release-version }} with: artifacts: "apps/cli/bw-oss-windows-${{ env.PKG_VERSION }}.zip, apps/cli/bw-oss-windows-sha256-${{ env.PKG_VERSION }}.txt, @@ -92,7 +92,8 @@ jobs: apps/cli/bw-linux-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bitwarden-cli.${{ env.PKG_VERSION }}.nupkg, apps/cli/bw_${{ env.PKG_VERSION }}_amd64.snap, - apps/cli/bw-snap-sha256-${{ env.PKG_VERSION }}.txt" + apps/cli/bw-snap-sha256-${{ env.PKG_VERSION }}.txt, + apps/cli/bitwarden-cli-${{ env.PKG_VERSION }}-npm-build.zip" commit: ${{ github.sha }} tag: cli-v${{ env.PKG_VERSION }} name: CLI v${{ env.PKG_VERSION }} diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index c9e1df94026..2fe7cb2b7a4 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -98,7 +98,7 @@ jobs: - name: Create Release uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 - if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' && github.event.inputs.github_release == 'true' }} + if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} env: PKG_VERSION: ${{ steps.version.outputs.version }} RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index f7d20044743..212795d3a2b 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -1,3 +1,4 @@ +--- name: Scan on: @@ -31,7 +32,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 #2.0.23 + uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 # v2.0.23 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4aa94a2ed0..52928e9a040 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,3 +1,4 @@ +--- name: Testing on: @@ -37,7 +38,7 @@ jobs: checks: write contents: read pull-requests: write - + steps: - name: Check out repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index d57fed94c12..88d53937f32 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "تسجيل الدخول" - }, "enterpriseSingleSignOn": { "message": "تسجيل الدخول الأُحادي للمؤسسات – SSO" }, @@ -1083,7 +1080,7 @@ "message": "1 جيغابايت وحدة تخزين مشفرة لمرفقات الملفات." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "خيارات تسجيل الدخول بخطوتين المملوكة لجهات اخرى مثل YubiKey و Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "شكرا لك على دعم Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "الكل فقط بـ $PRICE$ /سنة!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "اكتمل التحديث" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 0c181277ce2..32df9140afe 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Bir parol təyin edərək hesabınızı yaratmağı başa çatdırın" }, - "login": { - "message": "Giriş et" - }, "enterpriseSingleSignOn": { "message": "Müəssisə üçün tək daxil olma" }, @@ -1083,7 +1080,7 @@ "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi" }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Fövqəladə hal müraciəti" }, "premiumSignUpTwoStepOptions": { "message": "YubiKey və Duo kimi mülkiyyətçi iki addımlı giriş seçimləri." @@ -1107,7 +1104,7 @@ "message": "Premium üzvlüyü bitwarden.com veb anbarında satın ala bilərsiniz. İndi saytı ziyarət etmək istəyirsiniz?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Bitwarden veb tətbiqindəki hesab ayarlarınızda Premium satın ala bilərsiniz." }, "premiumCurrentMember": { "message": "Premium üzvsünüz!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Bitwarden-i dəstəklədiyiniz üçün təşəkkürlər!" }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Hamısı sadəcə ildə $PRICE$!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Təzələmə tamamlandı" }, @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Send uğurla yaradıldı!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Send, növbəti $DAYS$ ərzində keçidə sahib olan hər kəsə əlçatan olacaq.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send keçidi kopyalandı", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Səhifə yüklənəndə avto-doldurulsun?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Kart detalları" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index dcf5c21eed2..a49add09586 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Увайсці" - }, "enterpriseSingleSignOn": { "message": "Адзіны ўваход прадпрыемства (SSO)" }, @@ -1083,7 +1080,7 @@ "message": "1 ГБ зашыфраванага сховішча для далучаных файлаў." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Дзякуй за падтрымку Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Усяго за $PRICE$ у год!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Абнаўленне завершана" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 87f0a53a3e1..1d6f1cda360 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Завършете регистрацията си като зададете парола" }, - "login": { - "message": "Вписване" - }, "enterpriseSingleSignOn": { "message": "Еднократна идентификация (SSO)" }, @@ -1083,7 +1080,7 @@ "message": "1 GB пространство за файлове, които се шифрират." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Авариен достъп" }, "premiumSignUpTwoStepOptions": { "message": "Частно двустепенно удостоверяване чрез YubiKey и Duo." @@ -1107,7 +1104,7 @@ "message": "Може да платите абонамента си през сайта bitwarden.com. Искате ли да го посетите сега?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Можете да закупите платената версия от настройките на регистрацията си, в приложението по уеб на Битуорден." }, "premiumCurrentMember": { "message": "Честито, ползвате платен абонамент!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Благодарим ви за подкрепата на Bitwarden." }, + "premiumFeatures": { + "message": "Преминете към платената версия и ще получите:" + }, "premiumPrice": { "message": "И това само за $PRICE$ на година!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "И това само за $PRICE$ на година!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Абонаментът е опреснен" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Автоматично попълване при зареждане на страницата?" }, + "cardExpiredTitle": { + "message": "Изтекла карта" + }, + "cardExpiredMessage": { + "message": "Ако сте я подновили, актуализирайте информацията за картата" + }, "cardDetails": { "message": "Данни за картата" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 7881e0658d9..b570bf14b20 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "প্রবেশ করুন" - }, "enterpriseSingleSignOn": { "message": "এন্টারপ্রাইজ একক সাইন-অন" }, @@ -1083,7 +1080,7 @@ "message": "ফাইল সংযুক্তির জন্য ১ জিবি এনক্রিপ্টেড স্থান।" }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Bitwarden কে সমর্থন করার জন্য আপনাকে ধন্যবাদ।" }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "সমস্ত মাত্র $PRICE$ / বছরের জন্য!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "পুনঃসতেজ সম্পূর্ণ" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 0a72dbfa476..550b49f056a 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Prijavite se" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 3b961fa0042..0374ce8384f 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Acabeu de crear el vostre compte establint una contrasenya" }, - "login": { - "message": "Inicia sessió" - }, "enterpriseSingleSignOn": { "message": "Inici de sessió únic d'empresa" }, @@ -1083,7 +1080,7 @@ "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Opcions propietàries de doble factor com ara YubiKey i Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Gràcies per donar suport a Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Tot per només $PRICE$ / any!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Actualització completa" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index b22f6ff55f4..aff80372c65 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Dokončete vytváření účtu nastavením hesla" }, - "login": { - "message": "Přihlásit se" - }, "enterpriseSingleSignOn": { "message": "Jednotné podnikové přihlášení" }, @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Děkujeme za podporu Bitwardenu." }, + "premiumFeatures": { + "message": "Přejděte na Premium a získáte:" + }, "premiumPrice": { "message": "Vše jen za $PRICE$ ročně!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Vše jen za $PRICE$ ročně!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Obnova je dokončena" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Automaticky vyplnit při načtení stránky?" }, + "cardExpiredTitle": { + "message": "Prošlá karta" + }, + "cardExpiredMessage": { + "message": "Pokud jste ji obnovili, aktualizujte informace o kartě" + }, "cardDetails": { "message": "Podrobnosti karty" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 4d3f9a9b490..43eb9c41724 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Mewngofnodi" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "Storfa 1GB wedi'i hamgryptio ar gyfer atodiadau ffeiliau." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Dewisiadau mewngofnodi dau gam perchenogol megis YubiKey a Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Diolch am gefnogi Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Hyn oll am $PRICE$ y flwyddyn!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 8f35a21bb11..52f463a3a4b 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Afslut kontooprettelsen med at indstille en adgangskode" }, - "login": { - "message": "Log ind" - }, "enterpriseSingleSignOn": { "message": "Virksomheds Single-Sign-On" }, @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Tak fordi du støtter Bitwarden." }, + "premiumFeatures": { + "message": "Opgradér til Premium og modtag:" + }, "premiumPrice": { "message": "Alt dette for kun $PRICE$ /år!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Alt dette for kun $PRICE$ pr. år!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Opdatering færdig" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Udløbet kort" + }, + "cardExpiredMessage": { + "message": "Er det blevet fornyet, opdatér venligst kortoplysningerne" + }, "cardDetails": { "message": "Kortoplysninger" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 2c68a9e6db2..c5905ed913c 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Schließe die Erstellung deines Kontos ab, indem du ein Passwort festlegst" }, - "login": { - "message": "Anmelden" - }, "enterpriseSingleSignOn": { "message": "Enterprise Single-Sign-On" }, @@ -1083,7 +1080,7 @@ "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Notfallzugriff." }, "premiumSignUpTwoStepOptions": { "message": "Proprietäre Optionen für die Zwei-Faktor Authentifizierung wie YubiKey und Duo." @@ -1107,7 +1104,7 @@ "message": "Du kannst deine Premium-Mitgliedschaft im Bitwarden.com Web-Tresor kaufen. Möchtest du die Website jetzt besuchen?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Du kannst Premium über deine Kontoeinstellungen in der Bitwarden Web-App kaufen." }, "premiumCurrentMember": { "message": "Du bist jetzt Premium-Mitglied!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Vielen Dank, dass du Bitwarden unterstützt." }, + "premiumFeatures": { + "message": "Upgrade auf Premium und erhalte:" + }, "premiumPrice": { "message": "Das alles für %price% pro Jahr!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Alles für nur $PRICE$ pro Jahr!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Aktualisierung abgeschlossen" }, @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Send erfolgreich erstellt!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Das Send wird jedem mit dem Link für die nächsten $DAYS$ Tage zur Verfügung stehen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send-Link kopiert", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Auto-Ausfüllen beim Laden einer Seite?" }, + "cardExpiredTitle": { + "message": "Abgelaufene Karte" + }, + "cardExpiredMessage": { + "message": "Wenn du die Karte erneuert hast, aktualisiere die Angaben zur Karte" + }, "cardDetails": { "message": "Kartendetails" }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 2811dcf1b56..a29eaab7555 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Ολοκληρώστε τη δημιουργία του λογαριασμού σας ορίζοντας έναν κωδικό πρόσβασης" }, - "login": { - "message": "Σύνδεση" - }, "enterpriseSingleSignOn": { "message": "Ενιαία είσοδος για επιχειρήσεις" }, @@ -1083,7 +1080,7 @@ "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." }, "premiumSignUpEmergency": { - "message": "Πρόσβαση έκτακτης ανάγκης" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Πρόσθετες επιλογές σύνδεσης δύο βημάτων, όπως το YubiKey και το Duo." @@ -1107,7 +1104,7 @@ "message": "Μπορείτε να αγοράσετε συνδρομή Premium στο διαδικτυακό θησαυ/κιο του bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" }, "premiumPurchaseAlertV2": { - "message": "Μπορείτε να αγοράσετε το Premium από τις ρυθμίσεις λογαριασμού σας στην διαδικτυακή εφαρμογή Bitwarden." + "message": "You can purchase Premium from your account settings on the Bitwarden web app." }, "premiumCurrentMember": { "message": "Είστε Premium μέλος!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Ευχαριστούμε που υποστηρίζετε το Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Όλα για μόνο $PRICE$ /έτος!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Επιτυχής ανανέωση" }, @@ -2305,7 +2314,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Το Send δημιουργήθηκε επιτυχώς!", + "message": "Send created successfully!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Ο σύνδεσμος Send αντιγράφηκε", + "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Αυτόματη συμπλήρωση κατά τη φόρτωση της σελίδας;" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Στοιχεία κάρτας" }, diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b0df5fadd44..5ed28c6ec0e 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 5146e6151d7..533e271e6d5 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 8cb0875739a..feefa391c1a 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 2ab34997843..a9a8e115955 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Termina de crear tu cuenta estableciendo una contraseña" }, - "login": { - "message": "Iniciar sesión" - }, "enterpriseSingleSignOn": { "message": "Inicio de sesión único empresarial" }, @@ -1083,7 +1080,7 @@ "message": "1 GB de espacio cifrado en disco para adjuntos." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Opciones de inicio de sesión con autenticación de dos pasos propietarios como YubiKey y Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Gracias por apoyar el desarrollo de Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "¡Todo por solo %price% /año!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Actualización completada" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Datos de la tarjeta" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index d707cd5108f..c8bc75fa43e 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Lõpeta konto loomine parooli luues" }, - "login": { - "message": "Logi sisse" - }, "enterpriseSingleSignOn": { "message": "Ettevõtte ühekordne sisselogimine" }, @@ -1083,7 +1080,7 @@ "message": "1 GB ulatuses krüpteeritud salvestusruum." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Täname, et toetad Bitwardenit." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Kõik see ainult $PRICE$ / aastas!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Uuendamine lõpetatud" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index c407fcd74ea..f102af8442b 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Hasi saioa" - }, "enterpriseSingleSignOn": { "message": "Enpresentzako saio hasiera bakarra" }, @@ -1083,7 +1080,7 @@ "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Eskerrik asko Bitwarden babesteagatik." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Dena, urtean $PRICE$gatik!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Eguneratzea eginda" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index fa178ccbd82..109a5228a9e 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "ایجاد حساب خود را با تنظیم رمز عبور تکمیل کنید" }, - "login": { - "message": "ورود" - }, "enterpriseSingleSignOn": { "message": "ورود به سیستم پروژه" }, @@ -1083,7 +1080,7 @@ "message": "۱ گیگابایت فضای ذخیره سازی رمزگذاری شده برای پیوست های پرونده." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "برای حمایتتان از Bitwarden سپاسگزاریم." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "تمامش فقط $PRICE$ در سال!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "نوسازی کامل شد" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 5aa1a0d1388..1ca7d1ce15e 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Viimeistele tilin luonti asettamalla salasana" }, - "login": { - "message": "Kirjaudu" - }, "enterpriseSingleSignOn": { "message": "Yrityksen kertakirjautuminen (SSO)" }, @@ -311,13 +308,13 @@ "message": "Kansion nimi" }, "folderHintText": { - "message": "Luo alikansioita lisäämällä olemassa olevan kansion nimi merkin “/” jälkeen. Esim: Some/Foorumit" + "message": "Luo alikansio lisäämällä olemassa olevan kansion nimi \"/\"-merkin jälkeen. Esim: Some/Foorumit." }, "noFoldersAdded": { "message": "Kansioita ei ole lisätty" }, "createFoldersToOrganize": { - "message": "Luo kansioita järjestääksesi holvisi kohteita" + "message": "Lajittele holvisi kohteita luomalla kansioita" }, "deleteFolderPermanently": { "message": "Haluatko varmasti poistaa kansion pysyvästi?" @@ -1083,7 +1080,7 @@ "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Varmuuskäyttö" }, "premiumSignUpTwoStepOptions": { "message": "Omisteiset kaksivaiheisen kirjautumisen vaihtoehdot, kuten YubiKey ja Duo." @@ -1107,7 +1104,7 @@ "message": "Voit ostaa Premium-jäsenyyden bitwarden.com-verkkoholvista. Haluatko avata sivuston nyt?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Voit ostaa Premiumin tiliasetuksistasi Bitwardenin verkkosovelluksen kautta." }, "premiumCurrentMember": { "message": "Olet Premium-jäsen!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Kiitos kun tuet Bitwardenia." }, + "premiumFeatures": { + "message": "Päivitä premium-tilaukseen ja saat:" + }, "premiumPrice": { "message": "Kaikki tämä vain $PRICE$/vuosi!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Kaikki tämä vain $PRICE$/vuosi!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Päivitys valmistui" }, @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Sendin luonti onnistui!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "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": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send-linkki kopioitiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Automaattitäytetäänkö sivun avautuessa?" }, + "cardExpiredTitle": { + "message": "Kortti on vanhentunut" + }, + "cardExpiredMessage": { + "message": "Jos olet uusinut sen, päivitä kortin tiedot" + }, "cardDetails": { "message": "Kortin tiedot" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index e2f32ae5d9a..9f09fb6964d 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Mag-login" - }, "enterpriseSingleSignOn": { "message": "Enterprise Single Sign-On sa Filipino ay Isang Sign-On na Enterprise" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage para sa mga file attachment." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Pagmamay-ari na dalawang hakbang na opsyon sa pag-log in gaya ng YubiKey at Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Salamat sa pagsuporta sa Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Lahat para lamang sa $PRICE$ /taon!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "I-refresh ang lahat" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index bd799815c35..153c5710b0b 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Terminer la création de votre compte en définissant un mot de passe" }, - "login": { - "message": "Se connecter" - }, "enterpriseSingleSignOn": { "message": "Portail de connexion unique d'entreprise" }, @@ -1083,7 +1080,7 @@ "message": "1 Go de stockage chiffré pour les fichiers joints." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Options de connexion propriétaires à deux facteurs telles que YubiKey et Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Merci de soutenir Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Tout pour seulement $PRICE$/an !", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Actualisation terminée" }, @@ -1285,7 +1294,7 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Suggestions de saisie automatique" }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, @@ -3973,7 +3988,7 @@ } }, "addAccount": { - "message": "Add account" + "message": "Ajouter un compte" }, "loading": { "message": "Loading" diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 6084adadfe2..b57ca6c7017 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Iniciar sesión" - }, "enterpriseSingleSignOn": { "message": "Inicio de sesión único empresarial" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 82a1c59f7f7..df0a0b46d55 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "התחבר" - }, "enterpriseSingleSignOn": { "message": "כניסה ארגונית אחידה" }, @@ -1083,7 +1080,7 @@ "message": "1 ג'יגה של מקום אחסון עבור קבצים מצורפים." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "תודה על תמיכתך בBitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "הכל רק ב$PRICE$ לשנה!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "הרענון הושלם" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 3ec9630e470..9d65d502e21 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "पासवर्ड सेट करके अपना खाता निर्माण पूरा करें" }, - "login": { - "message": "Log In" - }, "enterpriseSingleSignOn": { "message": "उद्यम एकल साइन-ऑन" }, @@ -1083,7 +1080,7 @@ "message": "1 GB of encrypted file storage." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "ताज़ा पूरा" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 857310e21b4..e60676782a3 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Dovrši stvaranje svog računa postavljanjem lozinke" }, - "login": { - "message": "Prijava" - }, "enterpriseSingleSignOn": { "message": "Jedinstvena prijava na razini tvrtke (SSO)" }, @@ -1083,7 +1080,7 @@ "message": "1 GB šifriranog prostora za pohranu podataka." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Mogućnosti za prijavu u dva koraka kao što su YubiKey i Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Hvala ti što podupireš Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Sve za samo $PRICE$ /godišnje!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Osvježavanje završeno" }, @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send je uspješno stvoren!", + "message": "Send created successfully!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "message": "Send će biti dostupan svakome s poveznicom ovoliko dana: $DAYS$.", + "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Kopirana poveznica Senda", + "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Auto-ispuna kod učitavanja?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Detalji kartice" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 3cdbadfffdf..72d0b8a1887 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "A fiók létrehozásának befejezése jelszó beállításával" }, - "login": { - "message": "Bejelentkezés" - }, "enterpriseSingleSignOn": { "message": "Vállalati önálló bejelentkezés" }, @@ -1083,7 +1080,7 @@ "message": "1 GB titkosított tárhely a fájlmellékleteknek." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Sürgősségi hozzáférés" }, "premiumSignUpTwoStepOptions": { "message": "Saját kétlépcsős bejelentkezési lehetőségek mint a YubiKey és a Duo." @@ -1107,7 +1104,7 @@ "message": "A prémium tagság megvásárolható a bitwarden.com webes széfben. Szeretnénk felkeresni a webhelyet most?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Prémium szolgáltatást vásárolhatunk a Bitwarden webalkalmazás fiókbeállításai között." }, "premiumCurrentMember": { "message": "Prémium tag vagyunk!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Köszönjük a Bitwarden támogatását." }, + "premiumFeatures": { + "message": "Áttérés prémium verzióra és fogadás:" + }, "premiumPrice": { "message": "Mindez csak $PRICE$ /év.", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Mindez csak $PRICE$ /év!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Frissítés megtörtént" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Automatikus kitöltés oldalbetöltésnél?" }, + "cardExpiredTitle": { + "message": "Lejárt kártya" + }, + "cardExpiredMessage": { + "message": "Ha megújítottuk, frissítsük a kártya adatait." + }, "cardDetails": { "message": "Kártyaadatok" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 2eb1c089b97..b70e8d6788d 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Masuk" - }, "enterpriseSingleSignOn": { "message": "SSO Perusahaan" }, @@ -1083,7 +1080,7 @@ "message": "1 GB penyimpanan berkas yang dienkripsi." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Terima kasih telah mendukung Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Semua itu hanya $PRICE$ /tahun!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Penyegaran selesai" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 0c1eb9ef4f2..60ce1373d07 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Accedi" - }, "enterpriseSingleSignOn": { "message": "Single Sign-On aziendale" }, @@ -1083,7 +1080,7 @@ "message": "1 GB di spazio di archiviazione crittografato per gli allegati." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Opzioni di verifica in due passaggi proprietarie come YubiKey e Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Grazie per il tuo supporto a Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Il tutto per soli $PRICE$ all'anno!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Aggiornamento completato" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 9cf7d556a0e..0072b24fa5e 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "パスワードを設定してアカウントの作成を完了してください" }, - "login": { - "message": "ログイン" - }, "enterpriseSingleSignOn": { "message": "組織のシングルサインオン" }, @@ -1083,7 +1080,7 @@ "message": "1GB の暗号化されたファイルストレージ" }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "緊急アクセス" }, "premiumSignUpTwoStepOptions": { "message": "YubiKey、Duo などのプロプライエタリな2段階認証オプション。" @@ -1107,7 +1104,7 @@ "message": "プレミアム会員権は bitwarden.com ウェブ保管庫で購入できます。ウェブサイトを開きますか?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Bitwarden ウェブアプリでアカウント設定からプレミアムを購入できます。" }, "premiumCurrentMember": { "message": "あなたはプレミアム会員です!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Bitwarden を支援いただき、ありがとうございます。" }, + "premiumFeatures": { + "message": "プレミアムにアップグレードして受け取る:" + }, "premiumPrice": { "message": "全部でなんと$PRICE$/年だけ!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "すべて込みで年間たったの$PRICE$!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "更新完了" }, @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Send を作成しました!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Send は、次の $DAYS$ 日間はリンクを知っている人全員が利用できます。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send リンクをコピーしました", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "ページ読み込み時に自動入力する" }, + "cardExpiredTitle": { + "message": "期限切れのカード" + }, + "cardExpiredMessage": { + "message": "カードの更新があった場合、カード情報を更新してください" + }, "cardDetails": { "message": "カード情報" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index a55860c8ff9..d8e02622fd6 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "ავტორიზაცია" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 5503227cbaf..0700341d237 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 6c398b6aeb7..afcb7a17d0d 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "ಲಾಗಿನ್" - }, "enterpriseSingleSignOn": { "message": "ಎಂಟರ್‌ಪ್ರೈಸ್ ಏಕ ಸೈನ್-ಆನ್" }, @@ -1083,7 +1080,7 @@ "message": "ಫೈಲ್ ಲಗತ್ತುಗಳಿಗಾಗಿ 1 ಜಿಬಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹ." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "ಬಿಟ್ವಾರ್ಡೆನ್ ಅವರನ್ನು ಬೆಂಬಲಿಸಿದ್ದಕ್ಕಾಗಿ ಧನ್ಯವಾದಗಳು." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "ಎಲ್ಲವೂ ಕೇವಲ $PRICE$ / ವರ್ಷಕ್ಕೆ!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "ರಿಫ್ರೆಶ್ ಪೂರ್ಣಗೊಂಡಿದೆ" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 3b07fa86073..4325971967a 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "로그인" - }, "enterpriseSingleSignOn": { "message": "엔터프라이즈 통합 인증 (SSO)" }, @@ -1083,7 +1080,7 @@ "message": "1GB의 암호화된 파일 저장소." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Bitwarden을 지원해 주셔서 감사합니다." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "이 모든 기능을 연 $PRICE$에 이용하실 수 있습니다!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "새로 고침 완료" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index ac58c6df12a..d41ce8ec216 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Baigkite kurti paskyrą nustatydami slaptažodį" }, - "login": { - "message": "Prisijungti" - }, "enterpriseSingleSignOn": { "message": "Vienkartinis įmonės prisijungimas" }, @@ -1083,7 +1080,7 @@ "message": "1 GB užšifruotos vietos diske bylų prisegimams." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Patentuotos dviejų žingsnių prisijungimo parinktys, tokios kaip YubiKey ir Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Dėkojame, kad palaikote „Bitwarden“." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Visa tai tik už $PRICE$ / metus!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Atnaujinimas įvykdytas" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Kortelės duomenys" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index f43955f7a6e..75725ebb3a8 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Sava konta izveidošana jāpabeidz ar paroles iestatīšanu" }, - "login": { - "message": "Pieteikties" - }, "enterpriseSingleSignOn": { "message": "Uzņēmuma vienotā pieteikšanās" }, @@ -1083,7 +1080,7 @@ "message": "1 GB šifrētas krātuves datņu pielikumiem." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Ārkārtas piekļuve" }, "premiumSignUpTwoStepOptions": { "message": "Tādas slēgtā pirmavota divpakāpju pieteikšanās iespējas kā YubiKey un Duo." @@ -1107,7 +1104,7 @@ "message": "Premium dalību ir iespējams iegādāties bitwarden.com tīmekļa glabātavā. Vai tagad apmeklēt tīmekļvietni?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Premium var iegādāties Bitwarden tīmekļa lietotnē sava konta iestatījumos." }, "premiumCurrentMember": { "message": "Tu esi Premium dalībnieks!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Paldies, ka atbalsti Bitwarden!" }, + "premiumFeatures": { + "message": "Uzlabo uz \"Premium\" un saņem:" + }, "premiumPrice": { "message": "Viss par tikai $PRICE$ gadā!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Viss par tikai $PRICE$ gadā.", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Atsvaidzināšana pabeigta" }, @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Send tika veiksmīgi izveidots.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Send būs pieejams nākamās $DAYS$ dienas ikvienam, kuram ir saite.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send saite ievietota starpliktuvē", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Automātiski aizpildīt lapas ielādes brīdī?" }, + "cardExpiredTitle": { + "message": "Beidzies kartes derīgums" + }, + "cardExpiredMessage": { + "message": "Ja atjaunoji to, jāatjaunina kartes informācija" + }, "cardDetails": { "message": "Kartes dati" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index aa9247fb37a..2dcf65d111e 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "ലോഗിൻ" - }, "enterpriseSingleSignOn": { "message": "എന്റർപ്രൈസ് സിംഗിൾ സൈൻ-ഓൺ" }, @@ -1083,7 +1080,7 @@ "message": "ഫയൽ അറ്റാച്ചുമെന്റുകൾക്കായി 1 ജിബി എൻക്രിപ്റ്റുചെയ്‌ത സംഭരണം." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Bitwardenനെ പിന്തുണച്ചതിന് നന്ദി." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "എല്ലാം വെറും $PRICE$/ വർഷത്തേക്ക്!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "റിഫ്രഷ് പൂർത്തിയായി" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 0dd108a577a..39941ed1a11 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "प्रवेश करा" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 5503227cbaf..0700341d237 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 6f9a8eacb27..b9fd5fd32a7 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Logg inn" - }, "enterpriseSingleSignOn": { "message": "Bedriftsinnlogging (SSO)" }, @@ -1083,7 +1080,7 @@ "message": "1 GB med kryptert fillagring for filvedlegg." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Takk for at du støtter Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Og alt det for %price%/år!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Oppfriskning fullført" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 5503227cbaf..0700341d237 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 9f4cfc9888a..1b29416c1f1 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Rond het aanmaken van je account af met het instellen van een wachtwoord" }, - "login": { - "message": "Inloggen" - }, "enterpriseSingleSignOn": { "message": "Single sign-on voor bedrijven" }, @@ -1083,7 +1080,7 @@ "message": "1 GB versleutelde opslag voor bijlagen." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Noodtoegang" }, "premiumSignUpTwoStepOptions": { "message": "Eigen opties voor tweestapsaanmelding zoals YubiKey en Duo." @@ -1107,7 +1104,7 @@ "message": "Je kunt een Premium-abonnement aanschaffen in de webkluis op bitwarden.com. Wil je de website nu bezoeken?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Je kunt Premium via je accountinstellingen in de Bitwarden-webapp kopen." }, "premiumCurrentMember": { "message": "Je bent Premium-lid!" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Bedankt voor het ondersteunen van Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade naar premium en ontvang:" + }, "premiumPrice": { "message": "Dit alles voor slechts $PRICE$ per jaar!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Dit alles voor slechts $PRICE$ per jaar!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Bijwerken voltooid" }, @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Send succesvol aangemaakt!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "De Send is de komende $DAYS$ dagen beschikbaar voor iedereen met de link.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send-link gekopieerd", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Automatisch invullen bij laden van pagina?" }, + "cardExpiredTitle": { + "message": "Verlopen kaart" + }, + "cardExpiredMessage": { + "message": "Als je het hebt vernieuwd, werk dan de informatie van de kaart bij" + }, "cardDetails": { "message": "Kaartgegevens" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 5503227cbaf..0700341d237 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 5503227cbaf..0700341d237 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 482f07671c1..bded75e089c 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Ukończ tworzenie konta poprzez ustawienie hasła" }, - "login": { - "message": "Zaloguj się" - }, "enterpriseSingleSignOn": { "message": "Logowanie jednokrotne" }, @@ -1083,7 +1080,7 @@ "message": "1 GB miejsca na zaszyfrowane załączniki." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Własnościowe opcje logowania dwuetapowego, takie jak YubiKey i Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Dziękujemy za wspieranie Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Wszystko to jedynie za $PRICE$ /rok!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Odświeżanie zostało zakończone" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Włącz autouzupełnianie po załadowaniu strony?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Szczegóły karty" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index e7d20057c92..7829d7a59a4 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Termine de criar a sua conta definindo uma senha" }, - "login": { - "message": "Iniciar Sessão" - }, "enterpriseSingleSignOn": { "message": "Iniciar Sessão Empresarial Única" }, @@ -1083,7 +1080,7 @@ "message": "1 GB de armazenamento de arquivos encriptados." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Opções de login em duas etapas como YubiKey e Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Obrigado por apoiar o Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Tudo por apenas %price% /ano!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Atualização completa" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Preenchimento automático ao carregar a página?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Detalhes do cartão" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index f08f0875a98..a40029dc1ac 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Termine a criação da sua conta definindo uma palavra-passe" }, - "login": { - "message": "Iniciar sessão" - }, "enterpriseSingleSignOn": { "message": "Início de sessão único para empresas" }, @@ -1083,7 +1080,7 @@ "message": "1 GB de armazenamento encriptado para anexos de ficheiros." }, "premiumSignUpEmergency": { - "message": "Acesso de emergência" + "message": "Acesso de emergência." }, "premiumSignUpTwoStepOptions": { "message": "Opções proprietárias de verificação de dois passos, como YubiKey e Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Obrigado por apoiar o Bitwarden." }, + "premiumFeatures": { + "message": "Atualize para o Premium e receba:" + }, "premiumPrice": { "message": "Tudo por apenas $PRICE$ /ano!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Tudo por apenas $PRICE$ por ano!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Atualização concluída" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Preencher automaticamente ao carregar a página?" }, + "cardExpiredTitle": { + "message": "Cartão expirado" + }, + "cardExpiredMessage": { + "message": "Se o renovou, atualize as informações do cartão" + }, "cardDetails": { "message": "Detalhes do cartão" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 2359101274d..2c0a570161f 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finalizați crearea contului prin setarea unei parole" }, - "login": { - "message": "Conectare" - }, "enterpriseSingleSignOn": { "message": "Conectare unică organizație" }, @@ -1083,7 +1080,7 @@ "message": "1 GB spațiu de stocare criptat pentru atașamente de fișiere." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Opțiuni brevetate de conectare cu doi factori, cum ar fi YubiKey și Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Vă mulțumim pentru susținerea Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Totul pentru doar %price% /an!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Actualizare completă" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 67bbdb3f43b..f115aa80329 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Завершите создание аккаунта, задав пароль" }, - "login": { - "message": "Войти" - }, "enterpriseSingleSignOn": { "message": "Единая корпоративная авторизация" }, @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Благодарим вас за поддержку Bitwarden." }, + "premiumFeatures": { + "message": "Перейдите на Премиум и получите:" + }, "premiumPrice": { "message": "Всего лишь $PRICE$ в год!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Всего лишь $PRICE$ в год!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Обновление завершено" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Автозаполнение при загрузке страницы?" }, + "cardExpiredTitle": { + "message": "Истек срок действия карты" + }, + "cardExpiredMessage": { + "message": "Если вы заменили карту, обновите информацию о ней" + }, "cardDetails": { "message": "Реквизиты карты" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 5c5cb238ab2..02ff1f43a30 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "පිවිසෙන්න" - }, "enterpriseSingleSignOn": { "message": "ව්යවසාය තනි සංඥා මත" }, @@ -1083,7 +1080,7 @@ "message": "ගොනු ඇමුණුම් සඳහා 1 GB සංකේතාත්මක ගබඩා." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "බිට්වර්ඩන්ට සහාය වීම ගැන ස්තූතියි." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "සියල්ල $PRICE$ /අවුරුද්ද සඳහා!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "සම්පූර්ණ නැවුම් කරන්න" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 85e80156e69..ef4f60b0c51 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Zdajte heslo na vytvorenie účtu" }, - "login": { - "message": "Prihlásiť sa" - }, "enterpriseSingleSignOn": { "message": "Jednotné prihlásenie pre podniky (SSO)" }, @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Ďakujeme, že podporujete Bitwarden." }, + "premiumFeatures": { + "message": "Povýšte na premium a získajte:" + }, "premiumPrice": { "message": "Všetko len za %price% /rok!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Všetko len za $PRICE$ ročne!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Obnova kompletná" }, @@ -2309,7 +2318,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "message": "Send bude k dispozícii každemu s odkazom po dobu $DAYS$ dní.", + "message": "Send bude k dispozícii každému s odkazom po dobu $DAYS$ dní.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Automaticky vyplniť pri načítaní stránky?" }, + "cardExpiredTitle": { + "message": "Exspirovaná karta" + }, + "cardExpiredMessage": { + "message": "Ak ste kartu obnovili, aktualizujte jej údaje" + }, "cardDetails": { "message": "Podrobnosti o karte" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 19fb63eca78..f32fbca0a18 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Prijavi se" - }, "enterpriseSingleSignOn": { "message": "Enkratna podjetniška prijava." }, @@ -1083,7 +1080,7 @@ "message": "1 GB šifriranega prostora za shrambo podatkov." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Hvala, ker podpirate Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Vse za samo $PRICE$ /leto!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Osveževanje zaključeno" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index ec6b5e98b88..5cd284196f4 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Завршите креирање налога постављањем лозинке" }, - "login": { - "message": "Пријавите се" - }, "enterpriseSingleSignOn": { "message": "Enterprise Једна Пријава" }, @@ -1083,7 +1080,7 @@ "message": "1ГБ шифровано складиште за прилоге." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Приоритарне опције пријаве у два корака као што су YubiKey и Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Хвала Вам за подршку Bitwarden-а." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Све за само $PRICE$ годишње!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Освежавање је завршено" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Ауто-попуњавање при учитавању странице?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Детаљи картице" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index e0265494f1b..b4b4a0adf29 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Logga in" - }, "enterpriseSingleSignOn": { "message": "Single Sign-On för företag" }, @@ -1083,7 +1080,7 @@ "message": "1 GB lagring av krypterade filer." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Nödåtkomst" }, "premiumSignUpTwoStepOptions": { "message": "Premium-alternativ för tvåstegsverifiering, såsom YubiKey och Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Tack för att du stödjer Bitwarden." }, + "premiumFeatures": { + "message": "Uppgradera till Premium och få:" + }, "premiumPrice": { "message": "Allt för endast $PRICE$/år!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Allt för endast $PRICE$ per år!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Uppdatering färdig" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 5503227cbaf..0700341d237 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "Log in" - }, "enterpriseSingleSignOn": { "message": "Enterprise single sign-on" }, @@ -1083,7 +1080,7 @@ "message": "1 GB encrypted storage for file attachments." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 3ce8155e8e6..f630cc928e4 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "เข้าสู่ระบบ" - }, "enterpriseSingleSignOn": { "message": "Enterprise Single Sign-On" }, @@ -1083,7 +1080,7 @@ "message": "1 GB of encrypted file storage." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Thank you for supporting bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "All for just $PRICE$ /year!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Refresh complete" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 045ca1057fc..776d098664a 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Parolanızı belirleyerek hesabınızı oluşturmayı tamamlayın" }, - "login": { - "message": "Giriş yap" - }, "enterpriseSingleSignOn": { "message": "Kurumsal tek oturum açma (SSO)" }, @@ -1083,7 +1080,7 @@ "message": "Dosya ekleri için 1 GB şifrelenmiş depolama." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Acil durum erişimi" }, "premiumSignUpTwoStepOptions": { "message": "YubiKey ve Duo gibi marka bazlı iki aşamalı giriş seçenekleri." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Bitwarden'ı desteklediğiniz için teşekkür ederiz." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Bunların hepsi sadece yılda $PRICE$!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Yenileme tamamlandı" }, @@ -3311,7 +3320,7 @@ "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Duo'yu başlatın ve oturum açmayı tamamlamak için adımları izleyin." + "message": "Duo'yu açın ve girişi tamamlamak için adımları izleyin." }, "duoRequiredForAccount": { "message": "Hesabınız için Duo iki adımlı giriş gereklidir." @@ -3323,7 +3332,7 @@ "message": "Uzantıyı dışarı al" }, "launchDuo": { - "message": "Duo'yu başlat" + "message": "Duo'yu aç" }, "importFormatError": { "message": "Veriler doğru biçimlendirilmemiş. Lütfen içe aktarma dosyanızı kontrol edin ve tekrar deneyin." @@ -3557,7 +3566,7 @@ "message": "konum" }, "useDeviceOrHardwareKey": { - "message": "Cihazınızı veya donanımsal anahtarınızı kullanın" + "message": "Cihazımı veya anahtar donanımımı kullanacağım" }, "justOnce": { "message": "Yalnızca bir defa" @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Sayfa yüklendiğinde otomatik doldur" }, + "cardExpiredTitle": { + "message": "Kartın süresi dolmuş" + }, + "cardExpiredMessage": { + "message": "Kartı yenilediyseniz kart bilgilerini güncelleyin" + }, "cardDetails": { "message": "Kart bilgileri" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b351a39bfa0..97158d37e4b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Завершіть створення облікового запису, встановивши пароль" }, - "login": { - "message": "Увійти" - }, "enterpriseSingleSignOn": { "message": "Єдиний корпоративний вхід (SSO)" }, @@ -1083,7 +1080,7 @@ "message": "1 ГБ зашифрованого сховища для файлів." }, "premiumSignUpEmergency": { - "message": "Екстрений доступ" + "message": "Екстрений доступ." }, "premiumSignUpTwoStepOptions": { "message": "Додаткові можливості двоетапної авторизації, як-от YubiKey та Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Дякуємо за підтримку Bitwarden." }, + "premiumFeatures": { + "message": "Передплатіть преміум та отримайте:" + }, "premiumPrice": { "message": "Всього лише $PRICE$ / за рік!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "Усе лише за $PRICE$ на рік!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Оновлення завершено" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Автоматично заповнювати під час завантаження сторінки?" }, + "cardExpiredTitle": { + "message": "Протермінована картка" + }, + "cardExpiredMessage": { + "message": "Якщо ви її поновили, оновіть інформацію" + }, "cardDetails": { "message": "Подробиці картки" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index d61e8941ee0..77d8a9de144 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Hoàn thành việc tạo tài khoản của bạn bằng cách đặt mật khẩu" }, - "login": { - "message": "Đăng nhập" - }, "enterpriseSingleSignOn": { "message": "Đăng nhập bằng tài khoản tổ chức" }, @@ -1083,7 +1080,7 @@ "message": "1GB bộ nhớ lưu trữ được mã hóa cho các tệp đính kèm." }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "Các tùy chọn xác minh hai bước như YubiKey và Duo." @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "Cảm ơn bạn vì đã hỗ trợ Bitwarden." }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "Tất cả chỉ với $PRICE$/năm!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "Làm mới hoàn tất" }, @@ -1450,7 +1459,7 @@ "message": "Thương hiệu" }, "expirationMonth": { - "message": "Tháng Hết Hạn" + "message": "Tháng hết hạn" }, "expirationYear": { "message": "Năm hết hạn" @@ -1976,16 +1985,16 @@ "message": "Mật khẩu chính bạn chọn không đáp ứng yêu cầu." }, "receiveMarketingEmailsV2": { - "message": "Nhận lời khuyên, thông báo và cơ hội nghiên cứu từ Bitwarden trong hộp thư đến của bạn." + "message": "Nhận đề xuất, thông báo và cơ hội nghiên cứu từ Bitwarden trong hộp thư đến của bạn." }, "unsubscribe": { - "message": "Huỷ đăng ký" + "message": "Hủy đăng ký" }, "atAnyTime": { "message": "bất cứ lúc nào." }, "byContinuingYouAgreeToThe": { - "message": "Bằng cách tiếp tục, bạn đồng ý" + "message": "Nếu tiếp tục, bạn đồng ý" }, "and": { "message": "và" @@ -2490,7 +2499,7 @@ "message": "Không tìm thấy danh tính duy nhất." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ hiện đang dùng SSO với khoá máy chủ tự lưu trữ. Từ giờ không cần mật khẩu chính để đăng nhập vào tổ chức này nữa.", + "message": "$ORGANIZATION$ đang sử dụng SSO với khóa máy chủ tự lưu trữ. Mật khẩu chính không còn cần để đăng nhập cho các thành viên của tổ chức này.", "placeholders": { "organization": { "content": "$1", @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Thông tin thẻ" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 5ae7d2cd2dc..d99bb8e9594 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "设置密码以完成账户的创建" }, - "login": { - "message": "登录" - }, "enterpriseSingleSignOn": { "message": "企业单点登录" }, @@ -1083,7 +1080,7 @@ "message": "1 GB 文件附件加密存储。" }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "紧急访问。" }, "premiumSignUpTwoStepOptions": { "message": "专有的两步登录选项,如 YubiKey 和 Duo。" @@ -1098,7 +1095,7 @@ "message": "优先客户支持。" }, "ppremiumSignUpFuture": { - "message": "未来会增加更多高级功能。敬请期待!" + "message": "未来的更多高级功能。敬请期待!" }, "premiumPurchase": { "message": "购买高级版" @@ -1107,7 +1104,7 @@ "message": "您可以在 bitwarden.com 网页版密码库购买高级会员。现在要访问吗?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "您可以在 Bitwarden 网页 App 的账户设置中购买高级版。" }, "premiumCurrentMember": { "message": "您目前是高级会员!" @@ -1115,8 +1112,20 @@ "premiumCurrentMemberThanks": { "message": "感谢您支持 Bitwarden。" }, + "premiumFeatures": { + "message": "升级到高级版并接收:" + }, "premiumPrice": { - "message": "只需 $PRICE$ /年!", + "message": "全部仅需 $PRICE$ /年!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, + "premiumPriceV2": { + "message": "全部仅需 $PRICE$ /年!", "placeholders": { "price": { "content": "$1", @@ -2305,11 +2314,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "Send 创建成功!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAvailability": { - "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": { @@ -2319,7 +2328,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "Send 链接已复制", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "页面加载时自动填充吗?" }, + "cardExpiredTitle": { + "message": "过期的支付卡" + }, + "cardExpiredMessage": { + "message": "如果您的支付卡已续期,请更新该卡的信息。" + }, "cardDetails": { "message": "支付卡详情" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 945b712eef7..a7a264ad47a 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -25,9 +25,6 @@ "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" }, - "login": { - "message": "登入" - }, "enterpriseSingleSignOn": { "message": "企業單一登入" }, @@ -1083,7 +1080,7 @@ "message": "用於檔案附件的 1 GB 加密儲存空間。" }, "premiumSignUpEmergency": { - "message": "Emergency access" + "message": "Emergency access." }, "premiumSignUpTwoStepOptions": { "message": "專有的兩步驟登入選項,例如 YubiKey 和 Duo。" @@ -1115,6 +1112,9 @@ "premiumCurrentMemberThanks": { "message": "感謝您支持 Bitwarden 。" }, + "premiumFeatures": { + "message": "Upgrade to premium and receive:" + }, "premiumPrice": { "message": "每年只需 $PRICE$!", "placeholders": { @@ -1124,6 +1124,15 @@ } } }, + "premiumPriceV2": { + "message": "All for just $PRICE$ per year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, "refreshComplete": { "message": "狀態更新完成" }, @@ -3960,6 +3969,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 950f3b8e275..5d2d2a38981 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -40,6 +40,7 @@ export type FocusedFieldData = { frameId?: number; accountCreationFieldType?: string; showInlineMenuAccountCreation?: boolean; + showPasskeys?: boolean; }; export type InlineMenuElementPosition = { @@ -211,6 +212,7 @@ export type OverlayBackgroundExtensionMessageHandlers = { }: BackgroundOnMessageHandlerParams) => void; collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; unlockCompleted: ({ message }: BackgroundMessageParam) => void; + doFullSync: () => void; addedCipher: () => void; addEditCipherSubmitted: () => void; editedCipher: () => void; diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 111871d57dc..2c9232d8b5c 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -2,6 +2,7 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { ExtensionCommand } from "@bitwarden/common/autofill/constants"; @@ -12,6 +13,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; @@ -55,6 +57,7 @@ describe("NotificationBackground", () => { const logService = mock(); const themeStateService = mock(); const configService = mock(); + const accountService = mock(); beforeEach(() => { notificationBackground = new NotificationBackground( @@ -69,6 +72,7 @@ describe("NotificationBackground", () => { logService, themeStateService, configService, + accountService, ); }); @@ -691,6 +695,13 @@ describe("NotificationBackground", () => { }); describe("saveOrUpdateCredentials", () => { + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + let getDecryptedCipherByIdSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; let updatePasswordSpy: jest.SpyInstance; @@ -727,6 +738,8 @@ describe("NotificationBackground", () => { updateWithServerSpy = jest.spyOn(cipherService, "updateWithServer"); folderExistsSpy = jest.spyOn(notificationBackground as any, "folderExists"); cipherEncryptSpy = jest.spyOn(cipherService, "encrypt"); + + accountService.activeAccount$ = activeAccountSubject; }); it("skips saving the cipher if the notification queue does not have a tab that is related to the sender", async () => { @@ -981,7 +994,7 @@ describe("NotificationBackground", () => { queueMessage, null, ); - expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView); + expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId"); expect(createWithServerSpy).toHaveBeenCalled(); expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "saveCipherAttemptCompleted", @@ -1020,7 +1033,7 @@ describe("NotificationBackground", () => { sendMockExtensionMessage(message, sender); await flushPromises(); - expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView); + expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId"); expect(createWithServerSpy).toThrow(errorMessage); expect(tabSendMessageSpy).not.toHaveBeenCalledWith(sender.tab, { command: "addedCipher", diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index a047a3533a0..25f45bd0659 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1,7 +1,8 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { @@ -90,6 +91,7 @@ export default class NotificationBackground { private logService: LogService, private themeStateService: ThemeStateService, private configService: ConfigService, + private accountService: AccountService, ) {} async init() { @@ -556,7 +558,11 @@ export default class NotificationBackground { return; } - const cipher = await this.cipherService.encrypt(newCipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const cipher = await this.cipherService.encrypt(newCipher, activeUserId); try { await this.cipherService.createWithServer(cipher); await BrowserApi.tabSendMessage(tab, { command: "saveCipherAttemptCompleted" }); @@ -594,7 +600,11 @@ export default class NotificationBackground { return; } - const cipher = await this.cipherService.encrypt(cipherView); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const cipher = await this.cipherService.encrypt(cipherView, activeUserId); try { // We've only updated the password, no need to broadcast editedCipher message await this.cipherService.updateWithServer(cipher); @@ -634,7 +644,13 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string) { const cipher = await this.cipherService.get(cipherId); if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + return await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); } return null; } diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index b2ad7128b65..5ac94582ce3 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -717,7 +717,7 @@ describe("OverlayBackground", () => { localData: { lastUsedDate: 222 }, name: "name-1", type: CipherType.Login, - login: { username: "username-1", uri: url }, + login: { username: "username-1", password: "password", uri: url }, }); const cardCipher = mock({ id: "id-2", @@ -752,6 +752,7 @@ describe("OverlayBackground", () => { type: CipherType.Login, login: { username: "username-5", + password: "password", uri: url, fido2Credentials: [ mock({ @@ -1116,6 +1117,7 @@ describe("OverlayBackground", () => { overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id, filledByCipherType: CipherType.Login, + showPasskeys: true, }); cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, passkeyCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); @@ -2517,6 +2519,7 @@ describe("OverlayBackground", () => { describe("extension messages that trigger an update of the inline menu ciphers", () => { const extensionMessages = [ + "doFullSync", "addedCipher", "addEditCipherSubmitted", "editedCipher", diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 6e7db21a6d4..a209523dc7c 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -139,6 +139,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.triggerDestroyInlineMenuListeners(sender.tab, message.subFrameData.frameId), collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), unlockCompleted: ({ message }) => this.unlockCompleted(message), + doFullSync: () => this.updateOverlayCiphers(true), addedCipher: () => this.updateOverlayCiphers(), addEditCipherSubmitted: () => this.updateOverlayCiphers(), editedCipher: () => this.updateOverlayCiphers(), @@ -455,18 +456,27 @@ export class OverlayBackground implements OverlayBackgroundInterface { continue; } - if (this.showCipherAsPasskey(cipher, domainExclusionsSet)) { - passkeyCipherData.push( - this.buildCipherData({ - inlineMenuCipherId, - cipher, - showFavicons, - hasPasskey: true, - }), + if (!this.showCipherAsPasskey(cipher, domainExclusionsSet)) { + inlineMenuCipherData.push( + this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), ); + continue; } - inlineMenuCipherData.push(this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons })); + passkeyCipherData.push( + this.buildCipherData({ + inlineMenuCipherId, + cipher, + showFavicons, + hasPasskey: true, + }), + ); + + if (cipher.login?.password && cipher.login.username) { + inlineMenuCipherData.push( + this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), + ); + } } if (passkeyCipherData.length) { @@ -485,7 +495,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param domainExclusions - The domain exclusions to check against */ private showCipherAsPasskey(cipher: CipherView, domainExclusions: Set | null): boolean { - if (cipher.type !== CipherType.Login) { + if (cipher.type !== CipherType.Login || !this.focusedFieldData?.showPasskeys) { return false; } diff --git a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index f373494d52d..1e5e880c675 100644 --- a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -241,7 +241,16 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi async pickCredential({ cipherIds, userVerification, + assumeUserPresence, }: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { + // NOTE: For now, we are defaulting to a userVerified status of `true` when the request + // is for a conditionally mediated authentication. This will allow for mediated conditional + // authentication to function without requiring user interaction. This is a product + // decision, rather than a decision based on the expected technical specifications. + if (assumeUserPresence && cipherIds.length === 1) { + return { cipherId: cipherIds[0], userVerified: userVerification }; + } + const data: BrowserFido2Message = { type: BrowserFido2MessageTypes.PickCredentialRequest, cipherIds, diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index 5a95b928994..0701ef5f65a 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -115,5 +115,7 @@ export default class AutofillField { showInlineMenuAccountCreation?: boolean; + showPasskeys?: boolean; + fieldQualifier?: AutofillFieldQualifierType; } diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 43e8ce6809c..8bd667c17fb 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -14,6 +14,7 @@ import { } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -80,6 +81,7 @@ export class Fido2Component implements OnInit, OnDestroy { private browserMessagingApi: ZonedMessageListenerService, private passwordRepromptService: PasswordRepromptService, private fido2UserVerificationService: Fido2UserVerificationService, + private accountService: AccountService, ) {} ngOnInit() { @@ -156,11 +158,15 @@ export class Fido2Component implements OnInit, OnDestroy { } case "PickCredentialRequest": { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId); return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); }), ); @@ -172,11 +178,15 @@ export class Fido2Component implements OnInit, OnDestroy { } case "InformExcludedCredentialRequest": { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId); return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); }), ); @@ -378,8 +388,12 @@ export class Fido2Component implements OnInit, OnDestroy { } private async createNewCipher(name: string, username: string) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.buildCipher(name, username); - const cipher = await this.cipherService.encrypt(this.cipher); + const cipher = await this.cipherService.encrypt(this.cipher, activeUserId); try { await this.cipherService.createWithServer(cipher); this.cipher.id = cipher.id; 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 5b641e46c18..d0d1a79e293 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -792,6 +792,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ focusedFieldRects: { width, height, top, left }, filledByCipherType: autofillFieldData?.filledByCipherType, showInlineMenuAccountCreation: autofillFieldData?.showInlineMenuAccountCreation, + showPasskeys: !!autofillFieldData?.showPasskeys, accountCreationFieldType, }; @@ -874,6 +875,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ this.inlineMenuFieldQualificationService.isFieldForLoginForm(autofillFieldData, pageDetails) ) { autofillFieldData.filledByCipherType = CipherType.Login; + autofillFieldData.showPasskeys = autofillFieldData.autoCompleteType.includes("webauthn"); return false; } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 3e0e2d42e8b..4081e0e0cd9 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -956,6 +956,7 @@ export default class MainBackground { this.collectionService, this.cryptoService, this.pinService, + this.accountService, ); this.individualVaultExportService = new IndividualVaultExportService( @@ -975,6 +976,7 @@ export default class MainBackground { this.cryptoFunctionService, this.collectionService, this.kdfConfigService, + this.accountService, ); this.exportService = new VaultExportService( @@ -1000,6 +1002,7 @@ export default class MainBackground { this.cipherService, this.fido2UserInterfaceService, this.syncService, + this.accountService, this.logService, ); const fido2ActiveRequestManager = new Fido2ActiveRequestManager(); @@ -1093,6 +1096,7 @@ export default class MainBackground { this.logService, this.themeStateService, this.configService, + this.accountService, ); this.filelessImporterBackground = new FilelessImporterBackground( diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.html b/apps/browser/src/billing/popup/settings/premium-v2.component.html index 2a98cffb0e0..7b781eafdb4 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.html +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.html @@ -9,13 +9,13 @@

{{ "premiumFeatures" | i18n }}

-
+
  • {{ "ppremiumSignUpStorage" | i18n }}
  • - {{ "ppremiumSignUpTwoStepOptions" | i18n }} + {{ "premiumSignUpTwoStepOptions" | i18n }}
  • {{ "premiumSignUpEmergency" | i18n }} diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.ts b/apps/browser/src/billing/popup/settings/premium-v2.component.ts index 456aa6dc9ac..ef4c39942a2 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -74,7 +74,7 @@ export class PremiumV2Component extends BasePremiumComponent { const formattedPrice = this.platformUtilsService.isSafari() ? thePrice.replace("$", "$$$") : thePrice; - this.priceString = i18nService.t("premiumPrice", formattedPrice); + this.priceString = i18nService.t("premiumPriceV2", formattedPrice); if (this.priceString.indexOf("%price%") > -1) { this.priceString = this.priceString.replace("%price%", thePrice); } diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html index cef86a38492..8a7bedf0882 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -17,7 +17,8 @@ [ngClass]="{ 'tw-invisible': loading }" >
    diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.ts b/apps/browser/src/platform/popup/layout/popup-page.component.ts index 50db3e370fc..7b4665040fb 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-page.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, Input, inject, signal } from "@angular/core"; +import { booleanAttribute, Component, inject, Input, signal } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -17,6 +17,9 @@ export class PopupPageComponent { @Input() loading = false; + @Input({ transform: booleanAttribute }) + disablePadding = false; + protected scrolled = signal(false); isScrolled = this.scrolled.asReadonly(); diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 688e7e72a07..79a2df30e65 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -9,6 +9,7 @@ import { unauthGuardFn, } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; +import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, @@ -91,7 +92,7 @@ import { SyncComponent } from "../vault/popup/settings/sync.component"; import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component"; import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component"; -import { extensionRefreshRedirect, extensionRefreshSwap } from "./extension-refresh-route-utils"; +import { extensionRefreshRedirect } from "./extension-refresh-route-utils"; import { debounceNavigationGuard } from "./services/debounce-navigation.service"; import { TabsV2Component } from "./tabs-v2.component"; import { TabsComponent } from "./tabs.component"; diff --git a/apps/browser/src/popup/extension-refresh-route-utils.ts b/apps/browser/src/popup/extension-refresh-route-utils.ts index 3c2ca33f86e..9d45d7d656d 100644 --- a/apps/browser/src/popup/extension-refresh-route-utils.ts +++ b/apps/browser/src/popup/extension-refresh-route-utils.ts @@ -1,32 +1,9 @@ -import { inject, Type } from "@angular/core"; -import { Route, Router, Routes, UrlTree } from "@angular/router"; +import { inject } from "@angular/core"; +import { Router, UrlTree } from "@angular/router"; -import { componentRouteSwap } from "@bitwarden/angular/utils/component-route-swap"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -/** - * Helper function to swap between two components based on the ExtensionRefresh feature flag. - * @param defaultComponent - The current non-refreshed component to render. - * @param refreshedComponent - The new refreshed component to render. - * @param options - The shared route options to apply to both components. - */ -export function extensionRefreshSwap( - defaultComponent: Type, - refreshedComponent: Type, - options: Route, -): Routes { - return componentRouteSwap( - defaultComponent, - refreshedComponent, - async () => { - const configService = inject(ConfigService); - return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh); - }, - options, - ); -} - /** * Helper function to redirect to a new URL based on the ExtensionRefresh feature flag. * @param redirectUrl - The URL to redirect to if the ExtensionRefresh flag is enabled. diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts new file mode 100644 index 00000000000..95288f6b411 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts @@ -0,0 +1,192 @@ +import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; +import { ActivatedRoute, Router } from "@angular/router"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +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 { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherFormConfig, CipherFormConfigService, CipherFormMode } from "@bitwarden/vault"; + +import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service"; +import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; +import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; +import { PopupCloseWarningService } from "../../../../../popup/services/popup-close-warning.service"; + +import { AddEditV2Component } from "./add-edit-v2.component"; + +// 'qrcode-parser' is used by `BrowserTotpCaptureService` but is an es6 module that jest can't compile. +// Mock the entire module here to prevent jest from throwing an error. I wasn't able to find a way to mock the +// `BrowserTotpCaptureService` where jest would not load the file in the first place. +jest.mock("qrcode-parser", () => {}); + +describe("AddEditV2Component", () => { + let component: AddEditV2Component; + let fixture: ComponentFixture; + + const buildConfigResponse = { originalCipher: {} } as CipherFormConfig; + const buildConfig = jest.fn((mode: CipherFormMode) => + Promise.resolve({ mode, ...buildConfigResponse }), + ); + const queryParams$ = new BehaviorSubject({}); + const disable = jest.fn(); + const navigate = jest.fn(); + const back = jest.fn().mockResolvedValue(null); + + beforeEach(async () => { + buildConfig.mockClear(); + disable.mockClear(); + navigate.mockClear(); + back.mockClear(); + + await TestBed.configureTestingModule({ + imports: [AddEditV2Component], + providers: [ + { provide: PlatformUtilsService, useValue: mock() }, + { provide: ConfigService, useValue: mock() }, + { provide: PopupRouterCacheService, useValue: { back } }, + { provide: PopupCloseWarningService, useValue: { disable } }, + { provide: Router, useValue: { navigate } }, + { provide: ActivatedRoute, useValue: { queryParams: queryParams$ } }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + }) + .overrideProvider(CipherFormConfigService, { + useValue: { + buildConfig, + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(AddEditV2Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe("query params", () => { + describe("mode", () => { + it("sets mode to `add` when no `cipherId` is provided", fakeAsync(() => { + queryParams$.next({}); + + tick(); + + expect(buildConfig.mock.lastCall[0]).toBe("add"); + expect(component.config.mode).toBe("add"); + })); + + it("sets mode to `edit` when `params.clone` is not provided", fakeAsync(() => { + queryParams$.next({ cipherId: "222-333-444-5555", clone: "true" }); + + tick(); + + expect(buildConfig.mock.lastCall[0]).toBe("clone"); + expect(component.config.mode).toBe("clone"); + })); + + it("sets mode to `edit` when `params.clone` is not provided", fakeAsync(() => { + buildConfigResponse.originalCipher = { edit: true } as Cipher; + queryParams$.next({ cipherId: "222-333-444-5555" }); + + tick(); + + expect(buildConfig.mock.lastCall[0]).toBe("edit"); + expect(component.config.mode).toBe("edit"); + })); + + it("sets mode to `partial-edit` when `config.originalCipher.edit` is false", fakeAsync(() => { + buildConfigResponse.originalCipher = { edit: false } as Cipher; + queryParams$.next({ cipherId: "222-333-444-5555" }); + + tick(); + + expect(buildConfig.mock.lastCall[0]).toBe("edit"); + expect(component.config.mode).toBe("partial-edit"); + })); + }); + }); + + describe("onCipherSaved", () => { + it("disables warning when in popout", async () => { + jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValueOnce(true); + + await component.onCipherSaved({ id: "123-456-789" } as CipherView); + + expect(disable).toHaveBeenCalled(); + }); + + it("calls `confirmNewCredentialResponse` when in fido2 popout", async () => { + // @ts-expect-error - `inFido2PopoutWindow` is a private getter, mock the response here + // for the test rather than setting up the dependencies. + jest.spyOn(component, "inFido2PopoutWindow", "get").mockReturnValueOnce(true); + + await component.onCipherSaved({ id: "123-456-789" } as CipherView); + + expect(BrowserPopupUtils.inPopout).toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); + }); + + it("closes single action popout", async () => { + jest.spyOn(BrowserPopupUtils, "inSingleActionPopout").mockReturnValueOnce(true); + jest.spyOn(BrowserPopupUtils, "closeSingleActionPopout").mockResolvedValue(); + + await component.onCipherSaved({ id: "123-456-789" } as CipherView); + + expect(BrowserPopupUtils.closeSingleActionPopout).toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); + }); + + it("navigates to view-cipher for new ciphers", async () => { + component.config.mode = "add"; + + await component.onCipherSaved({ id: "123-456-789" } as CipherView); + + expect(navigate).toHaveBeenCalledWith(["/view-cipher"], { + replaceUrl: true, + queryParams: { cipherId: "123-456-789" }, + }); + expect(back).not.toHaveBeenCalled(); + }); + + it("navigates to view-cipher for edit ciphers", async () => { + component.config.mode = "edit"; + + await component.onCipherSaved({ id: "123-456-789" } as CipherView); + + expect(navigate).not.toHaveBeenCalled(); + expect(back).toHaveBeenCalled(); + }); + }); + + describe("handleBackButton", () => { + it("disables warning and aborts fido2 popout", async () => { + // @ts-expect-error - `inFido2PopoutWindow` is a private getter, mock the response here + // for the test rather than setting up the dependencies. + jest.spyOn(component, "inFido2PopoutWindow", "get").mockReturnValueOnce(true); + jest.spyOn(BrowserFido2UserInterfaceSession, "abortPopout"); + + await component.handleBackButton(); + + expect(disable).toHaveBeenCalled(); + expect(BrowserFido2UserInterfaceSession.abortPopout).toHaveBeenCalled(); + expect(back).not.toHaveBeenCalled(); + }); + + it("closes single action popout", async () => { + jest.spyOn(BrowserPopupUtils, "inSingleActionPopout").mockReturnValueOnce(true); + jest.spyOn(BrowserPopupUtils, "closeSingleActionPopout").mockResolvedValue(); + + await component.handleBackButton(); + + expect(BrowserPopupUtils.closeSingleActionPopout).toHaveBeenCalled(); + expect(back).not.toHaveBeenCalled(); + }); + + it("navigates the user backwards", async () => { + await component.handleBackButton(); + + expect(back).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 6cf2ba0e7d9..b830ae75048 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -151,10 +151,10 @@ export class AddEditV2Component implements OnInit { constructor( private route: ActivatedRoute, - private popupRouterCacheService: PopupRouterCacheService, private i18nService: I18nService, private addEditFormConfigService: CipherFormConfigService, private popupCloseWarningService: PopupCloseWarningService, + private popupRouterCacheService: PopupRouterCacheService, private router: Router, ) { this.subscribeToParams(); @@ -183,11 +183,7 @@ export class AddEditV2Component implements OnInit { }; /** - * Navigates to previous view or view-cipher path - * depending on the history length. - * - * This can happen when history is lost due to the extension being - * forced into a popout window. + * Handle back button */ async handleBackButton() { if (this.inFido2PopoutWindow) { @@ -223,10 +219,18 @@ export class AddEditV2Component implements OnInit { return; } - await this.router.navigate(["/view-cipher"], { - replaceUrl: true, - queryParams: { cipherId: cipher.id }, - }); + // When the cipher is in edit / partial edit, the previous page was the view-cipher page. + // In the case of creating a new cipher, the user should go view-cipher page but we need to also + // remove it from the history stack. This avoids the user having to click back twice on the + // view-cipher page. + if (this.config.mode === "edit" || this.config.mode === "partial-edit") { + await this.popupRouterCacheService.back(); + } else { + await this.router.navigate(["/view-cipher"], { + replaceUrl: true, + queryParams: { cipherId: cipher.id }, + }); + } } subscribeToParams(): void { diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index a0ab3401f4d..8c8827336ea 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -3,9 +3,10 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ReactiveFormsModule } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { Observable, combineLatest, first, switchMap } from "rxjs"; +import { Observable, combineLatest, first, map, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; @@ -52,18 +53,24 @@ export class AssignCollections { private location: Location, private collectionService: CollectionService, private cipherService: CipherService, + private accountService: AccountService, route: ActivatedRoute, ) { - const $cipher: Observable = route.queryParams.pipe( + const cipher$: Observable = route.queryParams.pipe( switchMap(({ cipherId }) => this.cipherService.get(cipherId)), switchMap((cipherDomain) => - this.cipherService - .getKeyForCipherKeyDecryption(cipherDomain) - .then(cipherDomain.decrypt.bind(cipherDomain)), + this.accountService.activeAccount$.pipe( + map((account) => account?.id), + switchMap((userId) => + this.cipherService + .getKeyForCipherKeyDecryption(cipherDomain, userId) + .then(cipherDomain.decrypt.bind(cipherDomain)), + ), + ), ), ); - combineLatest([$cipher, this.collectionService.decryptedCollections$]) + combineLatest([cipher$, this.collectionService.decryptedCollections$]) .pipe(takeUntilDestroyed(), first()) .subscribe(([cipherView, collections]) => { let availableCollections = collections.filter((c) => !c.readOnly); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts index ef430bbc176..29793a41ec9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts @@ -5,10 +5,14 @@ import { ActivatedRoute, Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { ButtonComponent } from "@bitwarden/components"; import { CipherAttachmentsComponent } from "@bitwarden/vault"; @@ -46,6 +50,9 @@ describe("AttachmentsV2Component", () => { const navigate = jest.fn(); const back = jest.fn().mockResolvedValue(undefined); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { back.mockClear(); navigate.mockClear(); @@ -66,6 +73,10 @@ describe("AttachmentsV2Component", () => { queryParams, }, }, + { + provide: AccountService, + useValue: accountService, + }, ], }) .overrideComponent(AttachmentsV2Component, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index b028ffe5831..8c1e0641b03 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -5,10 +5,13 @@ import { BehaviorSubject } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -51,6 +54,9 @@ describe("OpenAttachmentsComponent", () => { const getOrganization = jest.fn().mockResolvedValue(org); const showFilePopoutMessage = jest.fn().mockReturnValue(false); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { openCurrentPagePopout.mockClear(); getCipher.mockClear(); @@ -82,6 +88,10 @@ describe("OpenAttachmentsComponent", () => { provide: FilePopoutUtilsService, useValue: { showFilePopoutMessage }, }, + { + provide: AccountService, + useValue: accountService, + }, ], }).compileComponents(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 03c29d3f9fe..118510695c5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -2,9 +2,11 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -48,6 +50,7 @@ export class OpenAttachmentsComponent implements OnInit { private toastService: ToastService, private i18nService: I18nService, private filePopoutUtilsService: FilePopoutUtilsService, + private accountService: AccountService, ) { this.billingAccountProfileStateService.hasPremiumFromAnySource$ .pipe(takeUntilDestroyed()) @@ -64,8 +67,11 @@ export class OpenAttachmentsComponent implements OnInit { } const cipherDomain = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); if (!cipher.organizationId) { diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 4857703d3b1..2bc3fcea2f9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -1,8 +1,10 @@ import { CommonModule } from "@angular/common"; import { booleanAttribute, Component, Input } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; @@ -50,6 +52,7 @@ export class ItemMoreOptionsComponent { private router: Router, private i18nService: I18nService, private vaultPopupAutofillService: VaultPopupAutofillService, + private accountService: AccountService, ) {} get canEdit() { @@ -76,7 +79,7 @@ export class ItemMoreOptionsComponent { } async doAutofillAndSave() { - await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher); + await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher, false); } /** @@ -108,7 +111,10 @@ export class ItemMoreOptionsComponent { */ async toggleFavorite() { this.cipher.favorite = !this.cipher.favorite; - const encryptedCipher = await this.cipherService.encrypt(this.cipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encryptedCipher = await this.cipherService.encrypt(this.cipher, activeUserId); await this.cipherService.updateWithServer(encryptedCipher); this.toastService.showToast({ variant: "success", diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index 6ac793e4d4d..e24723d5831 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -17,47 +17,50 @@ {{ description }}
- - - - {{ cipher.name }} - - - {{ cipher.subTitle }} - - - - - - - - - + + + + + {{ cipher.name }} + + + {{ cipher.subTitle }} + + + + + + + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 0f5287af349..615d37cb604 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -1,3 +1,4 @@ +import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; import { booleanAttribute, Component, EventEmitter, Input, Output } from "@angular/core"; import { Router, RouterLink } from "@angular/router"; @@ -6,6 +7,8 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { BadgeModule, + BitItemHeight, + BitItemHeightClass, ButtonModule, IconButtonModule, ItemModule, @@ -35,12 +38,16 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options ItemCopyActionsComponent, ItemMoreOptionsComponent, OrgIconDirective, + ScrollingModule, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", standalone: true, }) export class VaultListItemsContainerComponent { + protected ItemHeightClass = BitItemHeightClass; + protected ItemHeight = BitItemHeight; + /** * The list of ciphers to display. */ diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 15693ff18d0..9851b16aa41 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -3,15 +3,20 @@ import { ActivatedRoute, Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { Subject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; +import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service"; import { ViewV2Component } from "./view-v2.component"; // 'qrcode-parser' is used by `BrowserTotpCaptureService` but is an es6 module that jest can't compile. @@ -30,6 +35,12 @@ describe("ViewV2Component", () => { type: CipherType.Login, }; + const mockVaultPopupAutofillService = { + doAutofill: jest.fn(), + }; + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const mockCipherService = { get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }), getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), @@ -59,6 +70,11 @@ describe("ViewV2Component", () => { }, }, }, + { provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService }, + { + provide: AccountService, + useValue: accountService, + }, ], }).compileComponents(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index ccc2658e59e..dbdf7287dda 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -3,10 +3,12 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { Observable, switchMap } from "rxjs"; +import { firstValueFrom, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AUTOFILL_ID, SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -31,6 +33,7 @@ import { BrowserTotpCaptureService } from "../../../services/browser-totp-captur import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "./../../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "./../../../../../platform/popup/layout/popup-page.component"; +import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service"; @Component({ selector: "app-view-v2", @@ -58,6 +61,7 @@ export class ViewV2Component { organization$: Observable; folder$: Observable; collections$: Observable; + loadAction: typeof AUTOFILL_ID | typeof SHOW_AUTOFILL_BUTTON; constructor( private route: ActivatedRoute, @@ -67,6 +71,8 @@ export class ViewV2Component { private dialogService: DialogService, private logService: LogService, private toastService: ToastService, + private vaultPopupAutofillService: VaultPopupAutofillService, + private accountService: AccountService, ) { this.subscribeToParams(); } @@ -75,14 +81,19 @@ export class ViewV2Component { this.route.queryParams .pipe( switchMap(async (params): Promise => { + this.loadAction = params.action; return await this.getCipherData(params.cipherId); }), + switchMap(async (cipher) => { + this.cipher = cipher; + this.headerText = this.setHeader(cipher.type); + if (this.loadAction === AUTOFILL_ID || this.loadAction === SHOW_AUTOFILL_BUTTON) { + await this.vaultPopupAutofillService.doAutofill(this.cipher); + } + }), takeUntilDestroyed(), ) - .subscribe((cipher) => { - this.cipher = cipher; - this.headerText = this.setHeader(cipher.type); - }); + .subscribe(); } setHeader(type: CipherType) { @@ -103,7 +114,12 @@ export class ViewV2Component { async getCipherData(id: string) { const cipher = await this.cipherService.get(id); - return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + return await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); } async editCipher() { diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.ts b/apps/browser/src/vault/popup/components/vault/attachments.component.ts index d571c6affe6..ee6f1ac7d07 100644 --- a/apps/browser/src/vault/popup/components/vault/attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault/attachments.component.ts @@ -5,6 +5,7 @@ import { first } from "rxjs/operators"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -36,6 +37,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -49,6 +51,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.ts b/apps/browser/src/vault/popup/components/vault/collections.component.ts index b201e6ff338..0a420192c88 100644 --- a/apps/browser/src/vault/popup/components/vault/collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault/collections.component.ts @@ -5,6 +5,7 @@ import { first } from "rxjs/operators"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -28,6 +29,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On private location: Location, logService: LogService, configService: ConfigService, + accountService: AccountService, ) { super( collectionService, @@ -37,6 +39,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On organizationService, logService, configService, + accountService, ); } diff --git a/apps/browser/src/vault/popup/components/vault/password-history.component.ts b/apps/browser/src/vault/popup/components/vault/password-history.component.ts index d5c0ff7673d..bf1b4ea7717 100644 --- a/apps/browser/src/vault/popup/components/vault/password-history.component.ts +++ b/apps/browser/src/vault/popup/components/vault/password-history.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router"; import { first } from "rxjs/operators"; import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -18,10 +19,11 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent imple cipherService: CipherService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, + accountService: AccountService, private location: Location, private route: ActivatedRoute, ) { - super(cipherService, platformUtilsService, i18nService, window); + super(cipherService, platformUtilsService, i18nService, accountService, window); } async ngOnInit() { diff --git a/apps/browser/src/vault/popup/components/vault/share.component.ts b/apps/browser/src/vault/popup/components/vault/share.component.ts index 6aba1e00c04..44c0a24ab9b 100644 --- a/apps/browser/src/vault/popup/components/vault/share.component.ts +++ b/apps/browser/src/vault/popup/components/vault/share.component.ts @@ -4,6 +4,7 @@ import { first } from "rxjs/operators"; import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -25,6 +26,7 @@ export class ShareComponent extends BaseShareComponent implements OnInit { private route: ActivatedRoute, private router: Router, organizationService: OrganizationService, + accountService: AccountService, ) { super( collectionService, @@ -33,6 +35,7 @@ export class ShareComponent extends BaseShareComponent implements OnInit { cipherService, logService, organizationService, + accountService, ); } diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html index 3279d28e931..e402e131436 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html @@ -1,4 +1,4 @@ - + @@ -54,7 +54,7 @@
- +
- +
diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts index a2b778984d7..c0886264875 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts @@ -1,3 +1,4 @@ +import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -51,6 +52,7 @@ enum VaultState { RouterLink, VaultV2SearchComponent, NewItemDropdownV2Component, + ScrollingModule, ], providers: [VaultUiOnboardingService], }) diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts index f3d95d3d203..f8e7de21dc8 100644 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view.component.ts @@ -2,12 +2,13 @@ import { DatePipe, Location } from "@angular/common"; import { ChangeDetectorRef, Component, NgZone, OnInit, OnDestroy } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { Subject, firstValueFrom, takeUntil, Subscription } from "rxjs"; -import { first } from "rxjs/operators"; +import { first, map } from "rxjs/operators"; import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -97,6 +98,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro fileDownloadService: FileDownloadService, dialogService: DialogService, datePipe: DatePipe, + accountService: AccountService, billingAccountProfileStateService: BillingAccountProfileStateService, ) { super( @@ -120,6 +122,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro fileDownloadService, dialogService, datePipe, + accountService, billingAccountProfileStateService, ); } @@ -267,7 +270,10 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.cipher.login.uris.push(loginUri); try { - const cipher: Cipher = await this.cipherService.encrypt(this.cipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher: Cipher = await this.cipherService.encrypt(this.cipher, activeUserId); await this.cipherService.updateWithServer(cipher); this.platformUtilsService.showToast( "success", diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index 6e74fd7c231..effadad07fb 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -1,11 +1,15 @@ import { TestBed } from "@angular/core/testing"; +import { ActivatedRoute } from "@angular/router"; import { mock } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { subscribeTo } from "@bitwarden/common/spec"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith, subscribeTo } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -30,6 +34,9 @@ describe("VaultPopupAutofillService", () => { let service: VaultPopupAutofillService; const mockCurrentTab = { url: "https://example.com" } as chrome.tabs.Tab; + const mockActivatedRoute = { + queryParams: of({}), + } as any; // Create mocks for VaultPopupAutofillService const mockAutofillService = mock(); @@ -40,6 +47,9 @@ describe("VaultPopupAutofillService", () => { const mockCipherService = mock(); const mockMessagingService = mock(); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(() => { jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false); jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(mockCurrentTab); @@ -55,6 +65,11 @@ describe("VaultPopupAutofillService", () => { { provide: PasswordRepromptService, useValue: mockPasswordRepromptService }, { provide: CipherService, useValue: mockCipherService }, { provide: MessagingService, useValue: mockMessagingService }, + { provide: ActivatedRoute, useValue: mockActivatedRoute }, + { + provide: AccountService, + useValue: accountService, + }, ], }); @@ -248,6 +263,17 @@ describe("VaultPopupAutofillService", () => { expect(setTimeout).toHaveBeenCalledTimes(1); expect(BrowserApi.closePopup).toHaveBeenCalled(); }); + + it("should show a successful toast message if login form is populated", async () => { + jest.spyOn(BrowserPopupUtils, "inSingleActionPopout").mockReturnValue(true); + (service as any).currentAutofillTab$ = of({ id: 1234 }); + await service.doAutofill(mockCipher); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: mockI18nService.t("autoFillSuccess"), + }); + }); }); }); @@ -311,7 +337,7 @@ describe("VaultPopupAutofillService", () => { expect(result).toBe(true); expect(mockCipher.login.uris).toHaveLength(1); expect(mockCipher.login.uris[0].uri).toBe(mockCurrentTab.url); - expect(mockCipherService.encrypt).toHaveBeenCalledWith(mockCipher); + expect(mockCipherService.encrypt).toHaveBeenCalledWith(mockCipher, mockUserId); expect(mockCipherService.updateWithServer).toHaveBeenCalledWith(mockEncryptedCipher); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index ca59ffd9979..a2e032a54f1 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -1,5 +1,7 @@ import { Injectable } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; import { + combineLatest, firstValueFrom, map, Observable, @@ -10,6 +12,7 @@ import { switchMap, } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -26,20 +29,30 @@ import { } from "../../../autofill/services/abstractions/autofill.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; +import { closeViewVaultItemPopout, VaultPopoutType } from "../utils/vault-popout-window"; @Injectable({ providedIn: "root", }) export class VaultPopupAutofillService { private _refreshCurrentTab$ = new Subject(); - + private senderTabId$: Observable = this.route.queryParams.pipe( + map((params) => (params?.senderTabId ? parseInt(params.senderTabId, 10) : undefined)), + ); /** - * Observable that contains the current tab to be considered for autofill. If there is no current tab - * or the popup is in a popout window, this will be null. + * Observable that contains the current tab to be considered for autofill. + * This can be the tab from the current window if opened in a Popup OR + * the sending tab when opened the single action Popout (specified by the senderTabId route query parameter) */ - currentAutofillTab$: Observable = this._refreshCurrentTab$.pipe( - startWith(null), - switchMap(async () => { + currentAutofillTab$: Observable = combineLatest([ + this.senderTabId$, + this._refreshCurrentTab$.pipe(startWith(null)), + ]).pipe( + switchMap(async ([senderTabId]) => { + if (senderTabId) { + return await BrowserApi.getTab(senderTabId); + } + if (BrowserPopupUtils.inPopout(window)) { return null; } @@ -72,6 +85,8 @@ export class VaultPopupAutofillService { private passwordRepromptService: PasswordRepromptService, private cipherService: CipherService, private messagingService: MessagingService, + private route: ActivatedRoute, + private accountService: AccountService, ) { this._currentPageDetails$.subscribe(); } @@ -122,7 +137,21 @@ export class VaultPopupAutofillService { return true; } - private _closePopup() { + private async _closePopup(cipher: CipherView, tab: chrome.tabs.Tab | null) { + if (BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.viewVaultItem) && tab.id) { + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("autoFillSuccess"), + }); + setTimeout(async () => { + await BrowserApi.focusTab(tab.id); + await closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${cipher.id}`); + }, 1000); + + return; + } + if (!BrowserPopupUtils.inPopup(window)) { return; } @@ -156,7 +185,7 @@ export class VaultPopupAutofillService { const didAutofill = await this._internalDoAutofill(cipher, tab, pageDetails); if (didAutofill && closePopup) { - this._closePopup(); + await this._closePopup(cipher, tab); } return didAutofill; @@ -191,7 +220,7 @@ export class VaultPopupAutofillService { } if (closePopup) { - this._closePopup(); + await this._closePopup(cipher, tab); } else { this.toastService.showToast({ variant: "success", @@ -221,7 +250,10 @@ export class VaultPopupAutofillService { cipher.login.uris.push(loginUri); try { - const encCipher = await this.cipherService.encrypt(cipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encCipher = await this.cipherService.encrypt(cipher, activeUserId); await this.cipherService.updateWithServer(encCipher); this.messagingService.send("editedCipher"); return true; diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 2ead93e5122..03e37fb71c9 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -30,6 +30,7 @@ describe("VaultPopupItemsService", () => { let mockOrg: Organization; let mockCollections: CollectionView[]; + let activeUserLastSync$: BehaviorSubject; const cipherServiceMock = mock(); const vaultSettingsServiceMock = mock(); @@ -92,7 +93,8 @@ describe("VaultPopupItemsService", () => { organizationServiceMock.organizations$ = new BehaviorSubject([mockOrg]); collectionService.decryptedCollections$ = new BehaviorSubject(mockCollections); - syncServiceMock.getLastSync.mockResolvedValue(new Date()); + activeUserLastSync$ = new BehaviorSubject(new Date()); + syncServiceMock.activeUserLastSync$.mockReturnValue(activeUserLastSync$); testBed = TestBed.configureTestingModule({ providers: [ @@ -161,7 +163,7 @@ describe("VaultPopupItemsService", () => { }); it("should not emit cipher list if syncService.getLastSync returns null", async () => { - syncServiceMock.getLastSync.mockResolvedValue(null); + activeUserLastSync$.next(null); const obs$ = service.autoFillCiphers$.pipe(timeout(50)); diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index e78e289c75a..be5c9087315 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -9,6 +9,7 @@ import { from, map, merge, + MonoTypeOperatorFunction, Observable, of, shareReplay, @@ -31,6 +32,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { runInsideAngular } from "../../../platform/browser/run-inside-angular.operator"; +import { waitUntil } from "../../util"; import { PopupCipherView } from "../views/popup-cipher.view"; import { VaultPopupAutofillService } from "./vault-popup-autofill.service"; @@ -80,8 +82,7 @@ export class VaultPopupItemsService { ).pipe( runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular tap(() => this._ciphersLoading$.next()), - switchMap(() => Utils.asyncToObservable(() => this.syncService.getLastSync())), - filter((lastSync) => lastSync !== null), // Only attempt to load ciphers if we performed a sync + waitUntilSync(this.syncService), switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), switchMap((ciphers) => combineLatest([ @@ -270,3 +271,11 @@ export class VaultPopupItemsService { return this.cipherService.sortCiphersByLastUsedThenName(a, b); } } + +/** + * Operator that waits until the active account has synced at least once before allowing the source to continue emission. + * @param syncService + */ +const waitUntilSync = (syncService: SyncService): MonoTypeOperatorFunction => { + return waitUntil(syncService.activeUserLastSync$().pipe(filter((lastSync) => lastSync != null))); +}; diff --git a/apps/browser/src/vault/util.ts b/apps/browser/src/vault/util.ts new file mode 100644 index 00000000000..f410375aa46 --- /dev/null +++ b/apps/browser/src/vault/util.ts @@ -0,0 +1,30 @@ +import { + merge, + MonoTypeOperatorFunction, + Observable, + ObservableInput, + sample, + share, + skipUntil, + take, +} from "rxjs"; + +/** + * Operator that waits until the trigger observable emits before allowing the source to continue emission. + * @param trigger$ The observable that will trigger the source to continue emission. + * + * ``` + * source$ a-----b-----c-----d-----e + * trigger$ ---------------X--------- + * output$ ---------------c--d-----e + * ``` + */ +export const waitUntil = (trigger$: ObservableInput): MonoTypeOperatorFunction => { + return (source: Observable) => { + const sharedSource$ = source.pipe(share()); + return merge( + sharedSource$.pipe(sample(trigger$), take(1)), + sharedSource$.pipe(skipUntil(trigger$)), + ); + }; +}; diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index d78f4cfa29c..bbd4241e21e 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -1,3 +1,6 @@ +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Response } from "../../models/response"; @@ -5,7 +8,10 @@ import { CliUtils } from "../../utils"; import { CipherResponse } from "../../vault/models/cipher.response"; export class ShareCommand { - constructor(private cipherService: CipherService) {} + constructor( + private cipherService: CipherService, + private accountService: AccountService, + ) {} async run(id: string, organizationId: string, requestJson: string): Promise { if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) { @@ -45,14 +51,18 @@ export class ShareCommand { if (cipher.organizationId != null) { return Response.badRequest("This item already belongs to an organization."); } + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); try { - await this.cipherService.shareWithServer(cipherView, organizationId, req); + await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); const updatedCipher = await this.cipherService.get(cipher.id); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 1bba149a35a..bac1cce7c75 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -1,5 +1,8 @@ +import { firstValueFrom, map } from "rxjs"; + import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { FolderExport } from "@bitwarden/common/models/export/folder.export"; @@ -24,6 +27,7 @@ export class EditCommand { private cryptoService: CryptoService, private apiService: ApiService, private folderApiService: FolderApiServiceAbstraction, + private accountService: AccountService, ) {} async run( @@ -77,18 +81,21 @@ export class EditCommand { return Response.notFound(); } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); let cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); if (cipherView.isDeleted) { return Response.badRequest("You may not edit a deleted item. Use the restore command first."); } cipherView = CipherExport.toView(req, cipherView); - const encCipher = await this.cipherService.encrypt(cipherView); + const encCipher = await this.cipherService.encrypt(cipherView, activeUserId); try { const updatedCipher = await this.cipherService.updateWithServer(encCipher); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); @@ -110,9 +117,12 @@ export class EditCommand { cipher.collectionIds = req; try { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 7e31750583b..923187bfcd0 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -6,6 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { CardExport } from "@bitwarden/common/models/export/card.export"; @@ -62,6 +63,7 @@ export class GetCommand extends DownloadCommand { private organizationService: OrganizationService, private eventCollectionService: EventCollectionService, private accountProfileService: BillingAccountProfileStateService, + private accountService: AccountService, ) { super(cryptoService); } @@ -110,9 +112,12 @@ export class GetCommand extends DownloadCommand { let decCipher: CipherView = null; if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); if (cipher != null) { decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); } } else if (id.trim() !== "") { diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 8a38f8f1280..6e0fa1c43c3 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -63,6 +63,7 @@ export class OssServeConfigurator { this.serviceContainer.organizationService, this.serviceContainer.eventCollectionService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); this.listCommand = new ListCommand( this.serviceContainer.cipherService, @@ -82,6 +83,7 @@ export class OssServeConfigurator { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.organizationService, + this.serviceContainer.accountService, ); this.editCommand = new EditCommand( this.serviceContainer.cipherService, @@ -89,6 +91,7 @@ export class OssServeConfigurator { this.serviceContainer.cryptoService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, + this.serviceContainer.accountService, ); this.generateCommand = new GenerateCommand( this.serviceContainer.passwordGenerationService, @@ -114,7 +117,10 @@ export class OssServeConfigurator { this.serviceContainer.organizationUserService, ); this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService); - this.shareCommand = new ShareCommand(this.serviceContainer.cipherService); + this.shareCommand = new ShareCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService); this.unlockCommand = new UnlockCommand( this.serviceContainer.accountService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 9cb70952593..c3fd55fa8b2 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -714,6 +714,7 @@ export class ServiceContainer { this.collectionService, this.cryptoService, this.pinService, + this.accountService, ); this.individualExportService = new IndividualVaultExportService( @@ -733,6 +734,7 @@ export class ServiceContainer { this.cryptoFunctionService, this.collectionService, this.kdfConfigService, + this.accountService, ); this.exportService = new VaultExportService( diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 670683e7a2a..05e7e7d22d7 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -149,6 +149,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.organizationService, this.serviceContainer.eventCollectionService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); const response = await cmd.run("template", object, null); this.processResponse(response); diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 04ca47ac1e1..9cf30086166 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -184,6 +184,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.organizationService, this.serviceContainer.eventCollectionService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); const response = await command.run(object, id, cmd); this.processResponse(response); @@ -227,6 +228,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.organizationService, + this.serviceContainer.accountService, ); const response = await command.run(object, encodedJson, cmd); this.processResponse(response); @@ -272,6 +274,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.cryptoService, this.serviceContainer.apiService, this.serviceContainer.folderApiService, + this.serviceContainer.accountService, ); const response = await command.run(object, id, encodedJson, cmd); this.processResponse(response); @@ -375,7 +378,10 @@ export class VaultProgram extends BaseProgram { }) .action(async (id, organizationId, encodedJson, cmd) => { await this.exitIfLocked(); - const command = new ShareCommand(this.serviceContainer.cipherService); + const command = new ShareCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); const response = await command.run(id, organizationId, encodedJson); this.processResponse(response); }); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 5db3bda97c2..0284ccc37bd 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -1,11 +1,12 @@ import * as fs from "fs"; import * as path from "path"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; @@ -34,6 +35,7 @@ export class CreateCommand { private folderApiService: FolderApiServiceAbstraction, private accountProfileService: BillingAccountProfileStateService, private organizationService: OrganizationService, + private accountService: AccountService, ) {} async run( @@ -80,11 +82,14 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const cipher = await this.cipherService.encrypt(CipherExport.toView(req)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); const decCipher = await newCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(newCipher), + await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId), ); const res = new CipherResponse(decCipher); return Response.success(res); @@ -143,13 +148,17 @@ export class CreateCommand { } try { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const updatedCipher = await this.cipherService.saveAttachmentRawWithServer( cipher, fileName, new Uint8Array(fileBuf).buffer, + activeUserId, ); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); return Response.success(new CipherResponse(decCipher)); } catch (e) { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 977a00b04dd..0056673a5a0 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2024.8.0", + "version": "2024.8.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.html b/apps/desktop/src/app/tools/import/import-desktop.component.html index 9fe2ee47c23..2bb715b5a46 100644 --- a/apps/desktop/src/app/tools/import/import-desktop.component.html +++ b/apps/desktop/src/app/tools/import/import-desktop.component.html @@ -1,4 +1,4 @@ - + {{ "importData" | i18n }} (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 519bd91064c..535aef307d7 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -164,7 +164,10 @@ export class EncryptedMessageHandlerService { cipherView.login.uris[0].uri = credentialCreatePayload.uri; try { - const encrypted = await this.cipherService.encrypt(cipherView); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encrypted = await this.cipherService.encrypt(cipherView, activeUserId); await this.cipherService.createWithServer(encrypted); // Notify other clients of new login @@ -197,14 +200,17 @@ export class EncryptedMessageHandlerService { if (cipher === null) { return { status: "failure" }; } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); cipherView.name = credentialUpdatePayload.name; cipherView.login.password = credentialUpdatePayload.password; cipherView.login.username = credentialUpdatePayload.userName; cipherView.login.uris[0].uri = credentialUpdatePayload.uri; - const encrypted = await this.cipherService.encrypt(cipherView); + const encrypted = await this.cipherService.encrypt(cipherView, activeUserId); await this.cipherService.updateWithServer(encrypted); diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index 8066da89a2e..b1ddcbc7e7d 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -28,6 +29,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -41,6 +43,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } } diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts index 4b6a88f325a..f659d4352c0 100644 --- a/apps/desktop/src/vault/app/vault/collections.component.ts +++ b/apps/desktop/src/vault/app/vault/collections.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { 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"; @@ -22,6 +23,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { organizationService: OrganizationService, logService: LogService, configService: ConfigService, + accountService: AccountService, ) { super( collectionService, @@ -31,6 +33,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { organizationService, logService, configService, + accountService, ); } } diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 44e2198a15d..12701ac5527 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -1,6 +1,7 @@ import { Component } from "@angular/core"; import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -14,7 +15,8 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent { cipherService: CipherService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, + accountService: AccountService, ) { - super(cipherService, platformUtilsService, i18nService, window); + super(cipherService, platformUtilsService, i18nService, accountService, window); } } diff --git a/apps/desktop/src/vault/app/vault/share.component.ts b/apps/desktop/src/vault/app/vault/share.component.ts index 95b22386e45..ddaad8337bc 100644 --- a/apps/desktop/src/vault/app/vault/share.component.ts +++ b/apps/desktop/src/vault/app/vault/share.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -21,6 +22,7 @@ export class ShareComponent extends BaseShareComponent { platformUtilsService: PlatformUtilsService, logService: LogService, organizationService: OrganizationService, + accountService: AccountService, private modalRef: ModalRef, ) { super( @@ -30,6 +32,7 @@ export class ShareComponent extends BaseShareComponent { cipherService, logService, organizationService, + accountService, ); } diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 64279d5fd40..140e1e9ced6 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -14,6 +14,7 @@ import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/com import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -62,6 +63,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro dialogService: DialogService, datePipe: DatePipe, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -84,6 +86,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro fileDownloadService, dialogService, datePipe, + accountService, billingAccountProfileStateService, ); } diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index be3bd0860f1..828fe8ea3fe 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -2,17 +2,7 @@ import { DOCUMENT } from "@angular/common"; import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core"; import { NavigationEnd, Router } from "@angular/router"; import * as jq from "jquery"; -import { - Subject, - combineLatest, - filter, - firstValueFrom, - map, - switchMap, - takeUntil, - timeout, - timer, -} from "rxjs"; +import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -25,8 +15,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -58,7 +46,6 @@ import { const BroadcasterSubscriptionId = "AppComponent"; const IdleTimeout = 60000 * 10; // 10 minutes -const PaymentMethodWarningsRefresh = 60000; // 1 Minute @Component({ selector: "app-root", @@ -69,7 +56,6 @@ export class AppComponent implements OnDestroy, OnInit { private idleTimer: number = null; private isIdle = false; private destroy$ = new Subject(); - private paymentMethodWarningsRefresh$ = timer(0, PaymentMethodWarningsRefresh); constructor( @Inject(DOCUMENT) private document: Document, @@ -98,7 +84,6 @@ export class AppComponent implements OnDestroy, OnInit { private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, - private paymentMethodWarningService: PaymentMethodWarningService, private organizationService: InternalOrganizationServiceAbstraction, private accountService: AccountService, ) {} @@ -252,25 +237,6 @@ export class AppComponent implements OnDestroy, OnInit { new DisableSendPolicy(), new SendOptionsPolicy(), ]); - - combineLatest([ - this.configService.getFeatureFlag$(FeatureFlag.ShowPaymentMethodWarningBanners), - this.paymentMethodWarningsRefresh$, - ]) - .pipe( - filter(([showPaymentMethodWarningBanners]) => showPaymentMethodWarningBanners), - switchMap(() => this.organizationService.memberOrganizations$), - switchMap( - async (organizations) => - await Promise.all( - organizations.map((organization) => - this.paymentMethodWarningService.update(organization.id), - ), - ), - ), - takeUntil(this.destroy$), - ) - .subscribe(); } ngOnDestroy() { @@ -328,7 +294,6 @@ export class AppComponent implements OnDestroy, OnInit { this.folderService.clear(userId), this.collectionService.clear(userId), this.biometricStateService.logout(userId), - this.paymentMethodWarningService.clear(), ]); await this.stateEventRunnerService.handleEvent("logout", userId); diff --git a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts index 09c9072cf0e..9d763886fb4 100644 --- a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -32,6 +33,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -45,6 +47,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts index 24970ee1cab..7dad7effeea 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts @@ -14,6 +14,7 @@ import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.res import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; import { BillingSharedModule, PaymentComponent, TaxInfoComponent } from "../../shared"; @@ -75,6 +76,7 @@ export class TrialBillingStepComponent implements OnInit { private messagingService: MessagingService, private organizationBillingService: OrganizationBillingService, private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) {} async ngOnInit(): Promise { @@ -96,11 +98,11 @@ export class TrialBillingStepComponent implements OnInit { const organizationId = await this.formPromise; const planDescription = this.getPlanDescription(); - this.platformUtilsService.showToast( - "success", - this.i18nService.t("organizationCreated"), - this.i18nService.t("organizationReadyToGo"), - ); + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("organizationCreated"), + message: this.i18nService.t("organizationReadyToGo"), + }); this.organizationCreated.emit({ organizationId, diff --git a/apps/web/src/app/billing/individual/premium.component.ts b/apps/web/src/app/billing/individual/premium.component.ts index b43d3cef342..79a5c5e2edd 100644 --- a/apps/web/src/app/billing/individual/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium.component.ts @@ -11,6 +11,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { ToastService } from "@bitwarden/components"; import { PaymentComponent, TaxInfoComponent } from "../shared"; @@ -46,6 +47,7 @@ export class PremiumComponent implements OnInit { private syncService: SyncService, private environmentService: EnvironmentService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private toastService: ToastService, ) { this.selfHosted = platformUtilsService.isSelfHost(); this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; @@ -75,11 +77,11 @@ export class PremiumComponent implements OnInit { this.addonForm.markAllAsTouched(); if (this.selfHosted) { if (this.licenseFile == null) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("selectFile"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("selectFile"), + }); return; } } @@ -87,11 +89,11 @@ export class PremiumComponent implements OnInit { if (this.selfHosted) { // eslint-disable-next-line @typescript-eslint/no-misused-promises if (!this.tokenService.getEmailVerified()) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("verifyEmailFirst"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("verifyEmailFirst"), + }); return; } @@ -130,7 +132,11 @@ export class PremiumComponent implements OnInit { async finalizePremium() { await this.apiService.refreshIdentityToken(); await this.syncService.fullSync(true); - this.platformUtilsService.showToast("success", null, this.i18nService.t("premiumUpdated")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("premiumUpdated"), + }); await this.router.navigate(["/settings/subscription/user-subscription"]); } diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 7e564341ca1..2d02cbc5bdf 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -10,7 +10,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { AdjustStorageDialogResult, @@ -48,6 +48,7 @@ export class UserSubscriptionComponent implements OnInit { private dialogService: DialogService, private environmentService: EnvironmentService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private toastService: ToastService, ) { this.selfHosted = platformUtilsService.isSelfHost(); } @@ -94,7 +95,11 @@ export class UserSubscriptionComponent implements OnInit { try { this.reinstatePromise = this.apiService.postReinstatePremium(); await this.reinstatePromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("reinstated")); + this.toastService.showToast({ + variant: "success", + 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(); diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index c98a6b97c41..226c92b45e3 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -6,6 +6,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-adjust-subscription", @@ -33,6 +34,7 @@ export class AdjustSubscription implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private organizationApiService: OrganizationApiServiceAbstraction, private formBuilder: FormBuilder, + private toastService: ToastService, ) {} ngOnInit() { @@ -76,7 +78,11 @@ export class AdjustSubscription implements OnInit, OnDestroy { ); await this.organizationApiService.updatePasswordManagerSeats(this.organizationId, request); - this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("subscriptionUpdated"), + }); this.onAdjusted.emit(); }; diff --git a/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts b/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts index 95a29229cf6..deb2c9da3ed 100644 --- a/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts +++ b/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts @@ -12,7 +12,7 @@ import { Verification } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface BillingSyncApiModalData { organizationId: string; @@ -43,6 +43,7 @@ export class BillingSyncApiKeyComponent { private i18nService: I18nService, private organizationApiService: OrganizationApiServiceAbstraction, private logService: LogService, + private toastService: ToastService, ) { this.organizationId = data.organizationId; this.hasBillingToken = data.hasBillingToken; @@ -67,11 +68,11 @@ export class BillingSyncApiKeyComponent { }); await this.load(response); this.showRotateScreen = false; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("billingSyncApiKeyRotated"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("billingSyncApiKeyRotated"), + }); } else { const response = await request.then((request) => { return this.organizationApiService.getOrCreateApiKey(this.organizationId, request); diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 995dcb23890..fe1c1568a9e 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -37,6 +37,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { OrgKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { ToastService } from "@bitwarden/components"; import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module"; import { BillingSharedModule, secretsManagerSubscribeFormFactory } from "../shared"; @@ -150,6 +151,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private organizationApiService: OrganizationApiServiceAbstraction, private providerApiService: ProviderApiServiceAbstraction, + private toastService: ToastService, ) { this.selfHosted = platformUtilsService.isSelfHost(); } @@ -582,18 +584,18 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]); } - this.platformUtilsService.showToast( - "success", - this.i18nService.t("organizationCreated"), - this.i18nService.t("organizationReadyToGo"), - ); + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("organizationCreated"), + message: this.i18nService.t("organizationReadyToGo"), + }); } else { orgId = await this.updateOrganization(orgId); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("organizationUpgraded"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("organizationUpgraded"), + }); } await this.apiService.refreshIdentityToken(); 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 c5ed013b1e4..b8616ae1b42 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 @@ -16,7 +16,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { AdjustStorageDialogResult, @@ -82,6 +82,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy private dialogService: DialogService, private configService: ConfigService, private providerService: ProviderService, + private toastService: ToastService, ) {} async ngOnInit() { @@ -378,7 +379,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy try { await this.organizationApiService.reinstate(this.organizationId); - this.platformUtilsService.showToast("success", null, this.i18nService.t("reinstated")); + this.toastService.showToast({ + variant: "success", + 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(); @@ -475,11 +480,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy try { await this.apiService.deleteRemoveSponsorship(this.organizationId); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("removeSponsorshipSuccess"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("removeSponsorshipSuccess"), + }); await this.load(); } catch (e) { this.logService.error(e); diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts index f2884a4fd05..3d2aef68755 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts @@ -16,7 +16,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { BillingSyncKeyComponent } from "./billing-sync-key.component"; @@ -84,6 +84,7 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest private i18nService: I18nService, private environmentService: EnvironmentService, private dialogService: DialogService, + private toastService: ToastService, ) {} async ngOnInit() { @@ -169,7 +170,11 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest this.load(); await this.loadOrganizationConnection(); this.messagingService.send("updatedOrgLicense"); - this.platformUtilsService.showToast("success", null, this.i18nService.t("licenseSyncSuccess")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("licenseSyncSuccess"), + }); }; get billingSyncSetUp() { diff --git a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts index 50abcc92ba7..bc8694a5058 100644 --- a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts @@ -6,6 +6,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; export interface SecretsManagerSubscriptionOptions { interval: "year" | "month"; @@ -100,6 +101,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest private organizationApiService: OrganizationApiServiceAbstraction, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) {} ngOnInit() { @@ -158,11 +160,11 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest request, ); - await this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("subscriptionUpdated"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("subscriptionUpdated"), + }); this.onAdjusted.emit(); }; diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts index 1f8b70e03fe..aae799d8089 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts @@ -11,6 +11,7 @@ import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/respon import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; import { secretsManagerSubscribeFormFactory } from "../shared"; @@ -33,6 +34,7 @@ export class SecretsManagerSubscribeStandaloneComponent { private i18nService: I18nService, private organizationApiService: OrganizationApiServiceAbstraction, private organizationService: InternalOrganizationServiceAbstraction, + private toastService: ToastService, ) {} submit = async () => { @@ -60,11 +62,11 @@ export class SecretsManagerSubscribeStandaloneComponent { */ await this.apiService.refreshIdentityToken(); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("subscribedToSecretsManager"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("subscribedToSecretsManager"), + }); this.onSubscribe.emit(); }; diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index 117f42fe397..c098b6044c8 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -18,6 +18,7 @@ import { PlanSponsorshipType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { ToastService } from "@bitwarden/components"; interface RequestSponsorshipForm { selectedSponsorshipOrgId: FormControl; @@ -51,6 +52,7 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { private organizationService: OrganizationService, private formBuilder: FormBuilder, private accountService: AccountService, + private toastService: ToastService, ) { this.sponsorshipForm = this.formBuilder.group({ selectedSponsorshipOrgId: new FormControl("", { @@ -118,7 +120,11 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { ); await this.formPromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("sponsorshipCreated")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("sponsorshipCreated"), + }); this.formPromise = null; // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index eff75b61b39..06dc1490e35 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -7,7 +7,7 @@ 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "[sponsoring-org-row]", @@ -30,6 +30,7 @@ export class SponsoringOrgRowComponent implements OnInit { private logService: LogService, private platformUtilsService: PlatformUtilsService, private dialogService: DialogService, + private toastService: ToastService, ) {} async ngOnInit() { @@ -53,7 +54,11 @@ export class SponsoringOrgRowComponent implements OnInit { async resendEmail() { await this.apiService.postResendSponsorshipOffer(this.sponsoringOrg.id); - this.platformUtilsService.showToast("success", null, this.i18nService.t("emailSent")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("emailSent"), + }); } get isSentAwaitingSync() { @@ -73,7 +78,11 @@ export class SponsoringOrgRowComponent implements OnInit { } await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id); - this.platformUtilsService.showToast("success", null, this.i18nService.t("reclaimedFreePlan")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("reclaimedFreePlan"), + }); this.sponsorshipRemoved.emit(); } diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog.component.ts index 1addf426293..08c9bf27660 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog.component.ts @@ -1,14 +1,11 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject, ViewChild } from "@angular/core"; import { FormGroup } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -46,7 +43,6 @@ export class AdjustPaymentDialogComponent { private apiService: ApiService, private i18nService: I18nService, private organizationApiService: OrganizationApiServiceAbstraction, - private paymentMethodWarningService: PaymentMethodWarningService, private configService: ConfigService, private toastService: ToastService, ) { @@ -78,12 +74,6 @@ export class AdjustPaymentDialogComponent { } }); await response; - const showPaymentMethodWarningBanners = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.ShowPaymentMethodWarningBanners), - ); - if (this.organizationId && showPaymentMethodWarningBanners) { - await this.paymentMethodWarningService.removeSubscriptionRisk(this.organizationId); - } this.toastService.showToast({ variant: "success", title: null, diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.ts b/apps/web/src/app/billing/shared/adjust-storage.component.ts index fcdbc3437df..439bfec82a0 100644 --- a/apps/web/src/app/billing/shared/adjust-storage.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage.component.ts @@ -10,7 +10,7 @@ import { StorageRequest } from "@bitwarden/common/models/request/storage.request import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { PaymentComponent } from "./payment.component"; @@ -56,6 +56,7 @@ export class AdjustStorageComponent { private activatedRoute: ActivatedRoute, private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, + private toastService: ToastService, ) { this.storageGbPrice = data.storageGbPrice; this.add = data.add; @@ -93,21 +94,21 @@ export class AdjustStorageComponent { await action(); this.dialogRef.close(AdjustStorageDialogResult.Adjusted); if (paymentFailed) { - this.platformUtilsService.showToast( - "warning", - null, - this.i18nService.t("couldNotChargeCardPayInvoice"), - { timeout: 10000 }, - ); + this.toastService.showToast({ + variant: "warning", + title: null, + message: this.i18nService.t("couldNotChargeCardPayInvoice"), + timeout: 10000, + }); // 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(["../billing"], { relativeTo: this.activatedRoute }); } else { - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), + }); } }; diff --git a/apps/web/src/app/billing/shared/index.ts b/apps/web/src/app/billing/shared/index.ts index edaf3a1199e..ae28e45f789 100644 --- a/apps/web/src/app/billing/shared/index.ts +++ b/apps/web/src/app/billing/shared/index.ts @@ -3,4 +3,3 @@ export * from "./payment-method.component"; export * from "./payment.component"; export * from "./sm-subscribe.component"; export * from "./tax-info.component"; -export * from "./payment-method-warnings/payment-method-warnings.module"; diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.ts b/apps/web/src/app/billing/shared/offboarding-survey.component.ts index 73a460f8c8f..7ffd40e058d 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.ts +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.ts @@ -5,7 +5,7 @@ import { FormBuilder, Validators } from "@angular/forms"; import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; type UserOffboardingParams = { type: "User"; @@ -88,6 +88,7 @@ export class OffboardingSurveyComponent { private billingApiService: BillingApiService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) {} submit = async () => { @@ -106,11 +107,11 @@ export class OffboardingSurveyComponent { ? await this.billingApiService.cancelOrganizationSubscription(this.dialogParams.id, request) : await this.billingApiService.cancelPremiumUserSubscription(request); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("canceledSubscription"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("canceledSubscription"), + }); this.dialogRef.close(this.ResultType.Submitted); }; diff --git a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.html b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.html deleted file mode 100644 index 59dbc5f976d..00000000000 --- a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.html +++ /dev/null @@ -1,15 +0,0 @@ - - - {{ "maintainYourSubscription" | i18n: warning.organizationName }} - {{ "addAPaymentMethod" | i18n }}. - - diff --git a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.ts b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.ts deleted file mode 100644 index d811961c0d1..00000000000 --- a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Component } from "@angular/core"; -import { map, Observable } from "rxjs"; - -import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction"; - -type Warning = { - organizationId: string; - organizationName: string; -}; - -@Component({ - selector: "app-payment-method-warnings", - templateUrl: "payment-method-warnings.component.html", -}) -export class PaymentMethodWarningsComponent { - constructor(private paymentMethodWarningService: PaymentMethodWarningService) {} - - protected warnings$: Observable = - this.paymentMethodWarningService.paymentMethodWarnings$.pipe( - map((warnings) => - Object.entries(warnings ?? []) - .filter(([_, warning]) => warning.risksSubscriptionFailure && !warning.acknowledged) - .map(([organizationId, { organizationName }]) => ({ - organizationId, - organizationName, - })), - ), - ); - - protected async closeWarning(organizationId: string): Promise { - await this.paymentMethodWarningService.acknowledge(organizationId); - } -} diff --git a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.module.ts b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.module.ts deleted file mode 100644 index c6303c878c4..00000000000 --- a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { BannerModule } from "@bitwarden/components"; - -import { SharedModule } from "../../../shared"; - -import { PaymentMethodWarningsComponent } from "./payment-method-warnings.component"; - -@NgModule({ - imports: [BannerModule, SharedModule], - declarations: [PaymentMethodWarningsComponent], - exports: [PaymentMethodWarningsComponent], -}) -export class PaymentMethodWarningsModule {} diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index eacc0b47390..0c089fa0733 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -13,7 +13,7 @@ import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank. import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component"; import { @@ -63,6 +63,7 @@ export class PaymentMethodComponent implements OnInit { private route: ActivatedRoute, private formBuilder: FormBuilder, private dialogService: DialogService, + private toastService: ToastService, ) {} async ngOnInit() { @@ -144,13 +145,21 @@ export class PaymentMethodComponent implements OnInit { request.amount1 = this.verifyBankForm.value.amount1; request.amount2 = this.verifyBankForm.value.amount2; await this.organizationApiService.verifyBank(this.organizationId, request); - this.platformUtilsService.showToast("success", null, this.i18nService.t("verifiedBankAccount")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("verifiedBankAccount"), + }); await this.load(); }; submitTaxInfo = async () => { await this.taxInfo.submitTaxInfo(); - this.platformUtilsService.showToast("success", null, this.i18nService.t("taxInfoUpdated")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("taxInfoUpdated"), + }); }; get isCreditBalance() { diff --git a/apps/web/src/app/billing/shared/tax-info.component.html b/apps/web/src/app/billing/shared/tax-info.component.html index c254ffa4a4d..89bc7438a77 100644 --- a/apps/web/src/app/billing/shared/tax-info.component.html +++ b/apps/web/src/app/billing/shared/tax-info.component.html @@ -13,7 +13,7 @@ -
+
{{ "zipPostalCode" | i18n }} diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts index fd9acfe7861..b663445028b 100644 --- a/apps/web/src/app/billing/shared/update-license-dialog.component.ts +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -6,7 +6,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { UpdateLicenseDialogResult } from "./update-license-types"; import { UpdateLicenseComponent } from "./update-license.component"; @@ -22,8 +22,16 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { platformUtilsService: PlatformUtilsService, organizationApiService: OrganizationApiServiceAbstraction, formBuilder: FormBuilder, + toastService: ToastService, ) { - super(apiService, i18nService, platformUtilsService, organizationApiService, formBuilder); + super( + apiService, + i18nService, + platformUtilsService, + organizationApiService, + formBuilder, + toastService, + ); } async submitLicense() { const result = await this.submit(); diff --git a/apps/web/src/app/billing/shared/update-license.component.ts b/apps/web/src/app/billing/shared/update-license.component.ts index 8dbb83c5104..e5421776846 100644 --- a/apps/web/src/app/billing/shared/update-license.component.ts +++ b/apps/web/src/app/billing/shared/update-license.component.ts @@ -6,6 +6,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; import { UpdateLicenseDialogResult } from "./update-license-types"; @@ -32,6 +33,7 @@ export class UpdateLicenseComponent implements OnInit { private platformUtilsService: PlatformUtilsService, private organizationApiService: OrganizationApiServiceAbstraction, private formBuilder: FormBuilder, + private toastService: ToastService, ) {} async ngOnInit() { const org = await this.organizationApiService.get(this.organizationId); @@ -52,11 +54,11 @@ export class UpdateLicenseComponent implements OnInit { } const files = this.licenseFile; if (files == null) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("selectFile"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("selectFile"), + }); return; } const fd = new FormData(); @@ -74,11 +76,11 @@ export class UpdateLicenseComponent implements OnInit { }); await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("licenseUploadSuccess"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("licenseUploadSuccess"), + }); this.onUpdated.emit(); return new Promise((resolve) => resolve(UpdateLicenseDialogResult.Updated)); }; diff --git a/apps/web/src/app/layouts/web-layout.component.html b/apps/web/src/app/layouts/web-layout.component.html index 31a5e826888..fc318a63989 100644 --- a/apps/web/src/app/layouts/web-layout.component.html +++ b/apps/web/src/app/layouts/web-layout.component.html @@ -1,9 +1,4 @@ - - - diff --git a/apps/web/src/app/layouts/web-layout.component.ts b/apps/web/src/app/layouts/web-layout.component.ts index bb91e619aad..840beaa2179 100644 --- a/apps/web/src/app/layouts/web-layout.component.ts +++ b/apps/web/src/app/layouts/web-layout.component.ts @@ -1,12 +1,8 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LayoutComponent } from "@bitwarden/components"; -import { PaymentMethodWarningsModule } from "../billing/shared"; - import { ProductSwitcherModule } from "./product-switcher/product-switcher.module"; import { ToggleWidthComponent } from "./toggle-width.component"; @@ -14,18 +10,8 @@ import { ToggleWidthComponent } from "./toggle-width.component"; selector: "app-layout", templateUrl: "web-layout.component.html", standalone: true, - imports: [ - CommonModule, - LayoutComponent, - ProductSwitcherModule, - ToggleWidthComponent, - PaymentMethodWarningsModule, - ], + imports: [CommonModule, LayoutComponent, ProductSwitcherModule, ToggleWidthComponent], }) export class WebLayoutComponent { - protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$( - FeatureFlag.ShowPaymentMethodWarningBanners, - ); - - constructor(private configService: ConfigService) {} + constructor() {} } diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 1a1daefb448..8e49c95e111 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -61,7 +61,6 @@ import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component" import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component"; import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; -import { PaymentMethodWarningsModule } from "../billing/shared"; import { DynamicAvatarComponent } from "../components/dynamic-avatar.component"; import { SelectableAvatarComponent } from "../components/selectable-avatar.component"; import { FrontendLayoutComponent } from "../layouts/frontend-layout.component"; @@ -113,7 +112,6 @@ import { SharedModule } from "./shared.module"; HeaderModule, OrganizationLayoutComponent, UserLayoutComponent, - PaymentMethodWarningsModule, VerifyRecoverDeleteOrgComponent, VaultTimeoutInputComponent, ], diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.html b/apps/web/src/app/vault/individual-vault/add-edit.component.html index 63136890071..b5a53bd1434 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.html +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.html @@ -23,6 +23,9 @@ {{ "personalOwnershipPolicyInEffect" | i18n }} + + {{ "cardExpiredMessage" | i18n }} +
diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 7a96bff0392..71ccaab7dd7 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -11,6 +11,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { EventType } from "@bitwarden/common/enums"; +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"; @@ -23,6 +24,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { DialogService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -43,6 +45,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On viewingPasswordHistory = false; viewOnly = false; showPasswordCount = false; + cardIsExpired: boolean = false; protected totpInterval: number; protected override componentName = "app-vault-add-edit"; @@ -115,6 +118,12 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On await this.totpTick(interval); }, 1000); } + + const extensionRefreshEnabled = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), + ); + + this.cardIsExpired = extensionRefreshEnabled && this.isCardExpiryInThePast(); } ngOnDestroy() { @@ -226,6 +235,24 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On this.viewingPasswordHistory = !this.viewingPasswordHistory; } + isCardExpiryInThePast() { + if (this.cipher.card) { + const { expMonth, expYear }: CardView = this.cipher.card; + + if (expYear && expMonth) { + // `Date` months are zero-indexed + const parsedMonth = parseInt(expMonth) - 1; + const parsedYear = parseInt(expYear); + + // First day of the next month minus one, to get last day of the card month + const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); + const now = new Date(); + + return cardExpiry < now; + } + } + } + protected cleanUp() { if (this.totpInterval) { window.clearInterval(this.totpInterval); diff --git a/apps/web/src/app/vault/individual-vault/attachments.component.ts b/apps/web/src/app/vault/individual-vault/attachments.component.ts index 3bf87ba4e3c..7a5706319ee 100644 --- a/apps/web/src/app/vault/individual-vault/attachments.component.ts +++ b/apps/web/src/app/vault/individual-vault/attachments.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -31,6 +32,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( cipherService, @@ -44,6 +46,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts index de935482b28..166f0feba4d 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts @@ -1,8 +1,10 @@ import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { 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"; @@ -61,6 +63,7 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { private collectionService: CollectionService, private organizationService: OrganizationService, private logService: LogService, + private accountService: AccountService, ) { this.ciphers = params.ciphers ?? []; this.organizationId = params.organizationId; @@ -98,10 +101,14 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { submit = async () => { const checkedCollectionIds = this.collections.filter(isChecked).map((c) => c.id); try { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); await this.cipherService.shareManyWithServer( this.shareableCiphers, this.organizationId, checkedCollectionIds, + activeUserId, ); const orgName = this.organizations.find((o) => o.id === this.organizationId)?.name ?? diff --git a/apps/web/src/app/vault/individual-vault/collections.component.ts b/apps/web/src/app/vault/individual-vault/collections.component.ts index 9795f879776..ed9dfbaf271 100644 --- a/apps/web/src/app/vault/individual-vault/collections.component.ts +++ b/apps/web/src/app/vault/individual-vault/collections.component.ts @@ -3,6 +3,7 @@ import { Component, Inject, OnDestroy } from "@angular/core"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { 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"; @@ -25,6 +26,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On organizationSerivce: OrganizationService, logService: LogService, configService: ConfigService, + accountService: AccountService, protected dialogRef: DialogRef, @Inject(DIALOG_DATA) params: CollectionsDialogParams, ) { @@ -36,6 +38,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On organizationSerivce, logService, configService, + accountService, ); this.cipherId = params?.cipherId; } diff --git a/apps/web/src/app/vault/individual-vault/share.component.ts b/apps/web/src/app/vault/individual-vault/share.component.ts index 6aef8c7097b..e7015fd7c37 100644 --- a/apps/web/src/app/vault/individual-vault/share.component.ts +++ b/apps/web/src/app/vault/individual-vault/share.component.ts @@ -2,6 +2,7 @@ import { Component, OnDestroy } from "@angular/core"; import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -21,6 +22,7 @@ export class ShareComponent extends BaseShareComponent implements OnDestroy { cipherService: CipherService, organizationService: OrganizationService, logService: LogService, + accountService: AccountService, ) { super( collectionService, @@ -29,6 +31,7 @@ export class ShareComponent extends BaseShareComponent implements OnDestroy { cipherService, logService, organizationService, + accountService, ); } diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 5a4a6794f3d..de945d52242 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -35,6 +35,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -198,6 +199,7 @@ export class VaultComponent implements OnInit, OnDestroy { private apiService: ApiService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -699,9 +701,12 @@ export class VaultComponent implements OnInit, OnDestroy { return; } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // Decrypt the cipher. const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); // Open the dialog. 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 8fd15cf20e8..6886da371ec 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 @@ -14,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -107,11 +108,12 @@ export class AddEditComponent extends BaseAddEditComponent { return cipher; } - protected encryptCipher() { + protected encryptCipher(userId: UserId) { if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) { - return super.encryptCipher(); + return super.encryptCipher(userId); } - return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher); + + return this.cipherService.encrypt(this.cipher, userId, null, null, this.originalCipher); } protected async deleteCipher() { diff --git a/apps/web/src/app/vault/org-vault/attachments.component.ts b/apps/web/src/app/vault/org-vault/attachments.component.ts index 71e7842913b..f02ac693108 100644 --- a/apps/web/src/app/vault/org-vault/attachments.component.ts +++ b/apps/web/src/app/vault/org-vault/attachments.component.ts @@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -12,6 +13,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic 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 { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -41,6 +43,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService: FileDownloadService, dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, private configService: ConfigService, ) { super( @@ -54,6 +57,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On fileDownloadService, dialogService, billingAccountProfileStateService, + accountService, ); } @@ -81,10 +85,11 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On return new Cipher(new CipherData(response)); } - protected saveCipherAttachment(file: File) { + protected saveCipherAttachment(file: File, userId: UserId) { return this.cipherService.saveAttachmentWithServer( this.cipherDomain, file, + userId, this.organization.canEditAllCiphers(this.restrictProviderAccess), ); } diff --git a/apps/web/src/app/vault/org-vault/collections.component.ts b/apps/web/src/app/vault/org-vault/collections.component.ts index 4ee052e32fe..e0c0ce91a7b 100644 --- a/apps/web/src/app/vault/org-vault/collections.component.ts +++ b/apps/web/src/app/vault/org-vault/collections.component.ts @@ -4,6 +4,7 @@ import { Component, Inject } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -37,6 +38,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { private apiService: ApiService, logService: LogService, configService: ConfigService, + accountService: AccountService, protected dialogRef: DialogRef, @Inject(DIALOG_DATA) params: OrgVaultCollectionsDialogParams, ) { @@ -48,6 +50,7 @@ export class CollectionsComponent extends BaseCollectionsComponent { organizationService, logService, configService, + accountService, dialogRef, params, ); diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 1e38cd152e9..6aae3cddd1d 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -39,6 +39,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -216,6 +217,7 @@ export class VaultComponent implements OnInit, OnDestroy { private organizationUserService: OrganizationUserService, protected configService: ConfigService, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -893,9 +895,12 @@ export class VaultComponent implements OnInit, OnDestroy { return; } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // Decrypt the cipher. const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); // Open the dialog. diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 0673b8bb76a..2d80f8b5d73 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Vervalmaand" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Bekyk item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nuut", "description": "for adding new items" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index a946211ee0d..cad9b5e6558 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "د" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "شهر انتهاء الصَّلاحِيَة" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "عرض العنصر" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "جديد", "description": "for adding new items" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 6984d000ba1..e6972df25dc 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Son istifadə ayı" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Elementə bax" }, + "viewItemType": { + "message": "$ITEMTYPE$ - bax", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Yeni", "description": "for adding new items" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 6c20081ead4..7c4a782c981 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Доктар" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Месяц завяршэння" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Прагледзець элемент" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Новы", "description": "for adding new items" diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 199144bf6bb..c847ccbf1f2 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Д-р" }, + "cardExpiredTitle": { + "message": "Изтекла карта" + }, + "cardExpiredMessage": { + "message": "Ако сте я подновили, актуализирайте информацията за картата" + }, "expirationMonth": { "message": "Месец на изтичане" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Преглед на елемента" }, + "viewItemType": { + "message": "Преглед на $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Ново", "description": "for adding new items" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 290ded160bd..b2232f425f7 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "ডাঃ" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "মেয়াদোত্তীর্ণ মাস" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "বস্তু দেখুন" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 7e69e71d770..a4a301e0d45 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mjesec Isteka" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Prikaz Stavke" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Novo", "description": "for adding new items" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index f5b246a5424..a252d27c988 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mes de venciment" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Visualitza l'element" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nou", "description": "for adding new items" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index b003c625d80..e3575fd1610 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "MUDr." }, + "cardExpiredTitle": { + "message": "Prošlá karta" + }, + "cardExpiredMessage": { + "message": "Pokud jste ji obnovili, aktualizujte informace o kartě" + }, "expirationMonth": { "message": "Měsíc expirace" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Zobrazit položku" }, + "viewItemType": { + "message": "Zobrazit $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nový", "description": "for adding new items" diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 82dad244d1a..0cae6bcd6ed 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index c958322042c..b59571ae88b 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Udløbet kort" + }, + "cardExpiredMessage": { + "message": "Er det blevet fornyet, opdatér venligst kortoplysningerne" + }, "expirationMonth": { "message": "Udløbsmåned" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Vis emne" }, + "viewItemType": { + "message": "Vis $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nyt", "description": "for adding new items" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index c5f48c66301..1519de5aea7 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Abgelaufene Karte" + }, + "cardExpiredMessage": { + "message": "Wenn du die Karte erneuert hast, aktualisiere die Angaben zur Karte" + }, "expirationMonth": { "message": "Ablaufmonat" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Eintrag anzeigen" }, + "viewItemType": { + "message": "$ITEMTYPE$ anzeigen", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Neu", "description": "for adding new items" @@ -1514,11 +1529,11 @@ "message": "Daten importieren" }, "onboardingImportDataDetailsPartOne": { - "message": "Wenn du keine Daten zu importieren hast, kannst du stattdessen einen ", + "message": "Wenn du keine Daten zu importieren hast, kannst du stattdessen ", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { - "message": "neuen Eintrag", + "message": "einen neuen Eintrag", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { @@ -4929,7 +4944,7 @@ "message": "Mitgliedern Zugriff geben:" }, "viewAndSelectTheMembers": { - "message": "Die Mitgliedern anzeigen und auswählen, denen du Zugriff auf den Secrets Manager gewähren möchtest." + "message": "Die Mitglieder anzeigen und auswählen, denen du Zugriff auf den Secrets Manager gewähren möchtest." }, "openYourOrganizations": { "message": "Open your organization's" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 3f3f1d35b21..06dd40ceae2 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Μήνας λήξης" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Προβολή Στοιχείου" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Νέο", "description": "for adding new items" diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index f7899eae3a0..108807b153f 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index c49710489e3..27698eaa11c 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 25b3350d44f..e1087d38d6d 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index ead5794bc9d..918c6070a67 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr-o" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Monato de validoperiodo" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Vidigi Artikolon" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index a0ac1f754c6..37184524be8 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mes de expiración" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Ver elemento" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nuevo", "description": "for adding new items" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index c535f1a889e..552a97b7ae4 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Aegumise kuu" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Kirje vaatamine" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Uus", "description": "for adding new items" diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index fdb9a2427f3..b0a1ed74816 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Jn." }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Iraungitze hilabetea" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Bistaratu elementua" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 3cc4d71eb9c..ce90b6c9e1b 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "دکتر" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "ماه انقضاء" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "مشاهده مورد" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "جدید", "description": "for adding new items" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 36fb9f73969..ab79c65c058 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -37,7 +37,7 @@ "message": "Merkinnät" }, "note": { - "message": "Merkintä" + "message": "Muistiinpano" }, "customFields": { "message": "Lisäkentät" @@ -68,7 +68,7 @@ } }, "websiteAdded": { - "message": "Verkkosivusto lisättiin" + "message": "Sivusto lisätty" }, "addWebsite": { "message": "Lisää verkkosivusto" @@ -194,6 +194,12 @@ "dr": { "message": "Tri" }, + "cardExpiredTitle": { + "message": "Vanhentunut kortti" + }, + "cardExpiredMessage": { + "message": "Jos olet uusinut sen, päivitä kortin tiedot" + }, "expirationMonth": { "message": "Erääntymiskuukausi" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Näytä kohde" }, + "viewItemType": { + "message": "Näytä $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Uusi", "description": "for adding new items" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index f23f4920f42..64fe74b527a 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr(a)" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Buwan ng pag-expire" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Tingnan ang item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Bago", "description": "for adding new items" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index a17fced7472..5b41ebbe84e 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Carte expirée" + }, + "cardExpiredMessage": { + "message": "Si vous l'avez renouvelée, mettez à jour les informations de la carte" + }, "expirationMonth": { "message": "Mois d'expiration" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Afficher l'élément" }, + "viewItemType": { + "message": "Voir $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nouveau", "description": "for adding new items" @@ -1167,7 +1182,7 @@ "message": "Êtes-vous sûr(e) de vouloir continuer ?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Choisissez un dossier dans lequel vous souhaitez ajouter le(s) $COUNT$ élément(s) sélectionné(s).", "placeholders": { "count": { "content": "$1", diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index f972a0e73c1..daceeeca49c 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Ver elemento" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Novo", "description": "for adding new items" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index bfa5ff1ba5f..a706124d4e3 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "דוקטור" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "תוקף אשראי - חודש" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "הצג פריט" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index bb3173fc51a..debe7d9c162 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "डॉ" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "समाप्ति माह" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 2497b9eb68c..f7aa923fb94 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "dr." }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mjesec isteka" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Prikaz stavke" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Novo", "description": "for adding new items" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index f58d778f332..970c1f43e83 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Lejárt kártya" + }, + "cardExpiredMessage": { + "message": "Ha megújítottuk, frissítsük a kártya adatait." + }, "expirationMonth": { "message": "Lejárati hónap" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Elem megtekintése" }, + "viewItemType": { + "message": "$ITEMTYPE$ megtekintése", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Új", "description": "for adding new items" diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 6d4ce09c3a0..0aefb63b362 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Bulan Kedaluwarsa" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Lihat Item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 6b3a5c0028a..ab7a078f452 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dott" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mese di scadenza" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Visualizza elemento" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nuovo", "description": "for adding new items" diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 03ace731e81..9204fe29832 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "期限切れのカード" + }, + "cardExpiredMessage": { + "message": "カードの更新があった場合、カード情報を更新してください" + }, "expirationMonth": { "message": "有効期限月" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "アイテムの表示" }, + "viewItemType": { + "message": "$ITEMTYPE$ を表示", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "新規作成", "description": "for adding new items" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index f70a5950a3c..d4dc0fea155 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "დოქტორი" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "ვადა გასვლის თვე" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "ნახვა საგნის" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "ახალი", "description": "for adding new items" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 2f801ac0158..b0f74968efa 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 2003846c63e..f7fb605afab 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "ಮುಕ್ತಾಯ ತಿಂಗಳು" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "ಐಟಂ ವೀಕ್ಷಿಸಿ" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 03bc47ff6dd..8137fa39042 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "만료 월" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "항목 보기" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "새 항목", "description": "for adding new items" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index e604add504f..ef6af194658 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Beidzies kartes derīgums" + }, + "cardExpiredMessage": { + "message": "Ja atjaunoji to, jāatjaunina kartes informācija" + }, "expirationMonth": { "message": "Derīguma mēnesis" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Skatīt vienumu" }, + "viewItemType": { + "message": "Apskatīt $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Jauns", "description": "for adding new items" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index b22d42f74bb..b37a4797aac 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "ഡോ" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "കാലാവതി കഴിയുന്ന മാസം" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "ഇനം കാണുക" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 2f801ac0158..b0f74968efa 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 2f801ac0158..b0f74968efa 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 55b7bedbcd9..8e6fc2c5505 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr․" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Utløpsmåned" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Vis elementet" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Ny", "description": "for adding new items" diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index fc582e83240..bb7ebcd4d37 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "डा" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "म्याद सकिने महिना" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index e8cfba484c4..6e96fdd1b24 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Verlopen kaart" + }, + "cardExpiredMessage": { + "message": "Als je het hebt vernieuwd, werk dan de informatie van de kaart bij" + }, "expirationMonth": { "message": "Vervalmaand" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Item weergeven" }, + "viewItemType": { + "message": "$ITEMTYPE$ weergeven", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nieuw", "description": "for adding new items" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 0bdb61b1abc..12200412aca 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr․" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Utløpsmånad" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Sjå oppføring" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 2f801ac0158..b0f74968efa 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 57dcb36d9d2..c460ed3f6dc 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Doktor" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Miesiąc wygaśnięcia" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Zobacz element" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nowy", "description": "for adding new items" diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index f308e262a95..077e6f0f6bb 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mês de Vencimento" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Visualizar Item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Novo", "description": "for adding new items" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index a6f3ea9d889..cde9fa3aa3c 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Cartão expirado" + }, + "cardExpiredMessage": { + "message": "Se o renovou, atualize as informações do cartão" + }, "expirationMonth": { "message": "Mês de validade" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Ver item" }, + "viewItemType": { + "message": "Ver $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Novo", "description": "for adding new items" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 04ec54750d7..81a09d0325d 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr." }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Luna expirării" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Afișare articol" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 36c36a58947..6594fab88ff 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Тов." }, + "cardExpiredTitle": { + "message": "Истек срок действия карты" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Месяц" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Просмотр элемента" }, + "viewItemType": { + "message": "Просмотр $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Новый", "description": "for adding new items" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 1dac83d3ec0..ba50b1d297d 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 469396c7a4b..dc547dc829f 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -58,7 +58,7 @@ "message": "Webová stránka (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Webová stránka (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expirovaná karta" + }, + "cardExpiredMessage": { + "message": "Ak ste kartu obnovili, aktualizujte jej údaje" + }, "expirationMonth": { "message": "Mesiac exspirácie" }, @@ -213,7 +219,7 @@ "message": "Bitwarden umožňuje uložiť a vyplniť kódy dvojstupňového overenia. Vyberte ikonu fotoaparátu a zosnímajte obrazovku QR kódu overovacej aplikácie tejto webovej stránky alebo skopírujte a vložte kľúč do tohto poľa." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Viac informácií o overovateľoch" }, "folder": { "message": "Priečinok" @@ -475,6 +481,15 @@ "viewItem": { "message": "Zobraziť položku" }, + "viewItemType": { + "message": "Zobraziť $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 0386539a6cd..e82043705e7 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mesec poteka" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Ogled elementa" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nov", "description": "for adding new items" diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 2360b44e1af..f59b398d76d 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Др" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Месец истека" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Види ставку" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Ново/а", "description": "for adding new items" diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index ebe3e186a20..31dd8ca74ba 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Mesec Isteka" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index c9c346a358e..ec7e3d882c9 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -46,13 +46,13 @@ "message": "Kortinnehavarens namn" }, "loginCredentials": { - "message": "Login credentials" + "message": "Inloggningsuppgifter" }, "authenticatorKey": { "message": "Autentiseringsnyckel" }, "autofillOptions": { - "message": "Autofill options" + "message": "Alternativ för autofyll" }, "websiteUri": { "message": "Webbplats (URI)" @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Utgångsmånad" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Visa objekt" }, + "viewItemType": { + "message": "Visa $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Nytt", "description": "for adding new items" diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 2f801ac0158..b0f74968efa 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "View item" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index d40dc14cabd..08445f7c530 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "ดร." }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "เดือนที่หมดอายุ" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "ดูรายการ" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "New", "description": "for adding new items" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 3019fd06e23..7f0440c4712 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Kartın süresi dolmuş" + }, + "cardExpiredMessage": { + "message": "Kartı yenilediyseniz kart bilgilerini güncelleyin" + }, "expirationMonth": { "message": "Son kullanma ayı" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Kaydı göster" }, + "viewItemType": { + "message": "$ITEMTYPE$ bilgileri", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Yeni", "description": "for adding new items" diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 14c1ec9725b..e3a93a405ed 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Доктор" }, + "cardExpiredTitle": { + "message": "Протермінована картка" + }, + "cardExpiredMessage": { + "message": "Якщо ви її поновили, оновіть інформацію" + }, "expirationMonth": { "message": "Місяць завершення" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Переглянути запис" }, + "viewItemType": { + "message": "Переглянути $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Новий", "description": "for adding new items" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index f787800f669..bcc21316ac6 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Tháng hết hạn" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "Xem mục" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "Mới", "description": "for adding new items" @@ -3889,7 +3904,7 @@ "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Hủy đăng ký" }, "atAnyTime": { "message": "at any time." diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 2fd288a1990..2ef18935e6b 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "博士" }, + "cardExpiredTitle": { + "message": "过期的支付卡" + }, + "cardExpiredMessage": { + "message": "如果您的支付卡已续期,请更新该卡的信息。" + }, "expirationMonth": { "message": "过期月份" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "查看项目" }, + "viewItemType": { + "message": "查看 $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "新增", "description": "for adding new items" @@ -2250,10 +2265,10 @@ "message": "优先客户支持。" }, "premiumSignUpFuture": { - "message": "未来会增加更多高级功能。敬请期待!" + "message": "未来的更多高级功能。敬请期待!" }, "premiumPrice": { - "message": "只需 $PRICE$ /年!", + "message": "全部仅需 $PRICE$ /年!", "placeholders": { "price": { "content": "$1", @@ -2528,7 +2543,7 @@ "message": "已更新付款方式。" }, "purchasePremium": { - "message": "购买高级会员" + "message": "购买高级版" }, "licenseFile": { "message": "许可证文件" @@ -4063,7 +4078,7 @@ "message": "您的 API 密钥可用于验证 Bitwarden 公共 API。" }, "apiKeyRotateDesc": { - "message": "轮换 API 密钥将使前一个密钥无效。如果你认为当前密钥不再安全,你可以轮换您的 API 密钥。" + "message": "轮换 API 密钥将使前一个密钥失效。如果您认为当前密钥不再安全,可以轮换 API 密钥。" }, "apiKeyWarning": { "message": "您的 API 密钥拥有组织的全部访问权限。请严格保密。" @@ -5347,7 +5362,7 @@ "message": "为允许的应用程序自动登录用户" }, "automaticAppLoginDesc": { - "message": "登录表单将自动填写并提交给从您配置的身份提供程序启动的 App。" + "message": "从您配置的身份提供程序启动的 App 的登录表单将自动填写并提交。" }, "automaticAppLoginIdpHostLabel": { "message": "身份提供程序主机" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index b5cb1f250ec..9508a4ddd28 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "逾期月份" }, @@ -475,6 +481,15 @@ "viewItem": { "message": "檢視項目" }, + "viewItemType": { + "message": "View $ITEMTYPE$", + "placeholders": { + "itemtype": { + "content": "$1", + "example": "login" + } + } + }, "new": { "message": "新增", "description": "for adding new items" 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 9733e91be7c..37ef3173602 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 @@ -7,7 +7,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component"; import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vault/app/billing"; -import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { @@ -53,7 +52,6 @@ import { SetupComponent } from "./setup/setup.component"; OrganizationPlansComponent, SearchModule, ProvidersLayoutComponent, - PaymentMethodWarningsModule, TaxInfoComponent, DangerZoneComponent, ScrollingModule, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index 3b81d0564c9..482b85b7127 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -1,6 +1,3 @@ - ({ name: algorithm, value: algorithm }), + ); + readonly saml2SigningBehaviourOptions: SelectOptions[] = [ { name: "If IdP Wants Authn Requests Signed", @@ -186,7 +189,6 @@ export class SsoComponent implements OnInit, OnDestroy { private i18nService: I18nService, private organizationService: OrganizationService, private organizationApiService: OrganizationApiServiceAbstraction, - private configService: ConfigService, ) {} async ngOnInit() { @@ -330,10 +332,6 @@ export class SsoComponent implements OnInit, OnDestroy { return this.ssoConfigForm.get("keyConnectorUrl"); } - get samlSigningAlgorithmOptions(): SelectOptions[] { - return this.samlSigningAlgorithms.map((algorithm) => ({ name: algorithm, value: algorithm })); - } - /** * Shows any validation errors for the form by marking all controls as dirty and touched. * If nested form groups are found, they are also updated. diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 5a2235e48a2..f185bed7e4a 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -1,7 +1,9 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { 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"; @@ -36,6 +38,7 @@ export class CollectionsComponent implements OnInit { protected organizationService: OrganizationService, private logService: LogService, private configService: ConfigService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -48,8 +51,11 @@ export class CollectionsComponent implements OnInit { async load() { this.cipherDomain = await this.loadCipher(); this.collectionIds = this.loadCipherCollections(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); this.collections = await this.loadCollections(); diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index 20fdebb71ec..f94a31e8d20 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -21,6 +21,7 @@ import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-con import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; 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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -72,6 +73,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements private ssoLoginService: SsoLoginServiceAbstraction, dialogService: DialogService, kdfConfigService: KdfConfigService, + private encryptService: EncryptService, ) { super( i18nService, @@ -160,7 +162,23 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements // Existing JIT provisioned user in a MP encryption org setting first password // Users in this state will not already have a user asymmetric key pair so must create it for them // We don't want to re-create the user key pair if the user already has one (TDE user case) - newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]); + + // in case we have a local private key, and are not sure whether it has been posted to the server, we post the local private key instead of generating a new one + const existingUserPrivateKey = (await firstValueFrom( + this.cryptoService.userPrivateKey$(this.userId), + )) as Uint8Array; + const existingUserPublicKey = await firstValueFrom( + this.cryptoService.userPublicKey$(this.userId), + ); + if (existingUserPrivateKey != null && existingUserPublicKey != null) { + const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey); + newKeyPair = [ + existingUserPublicKeyB64, + await this.encryptService.encrypt(existingUserPrivateKey, userKey[0]), + ]; + } else { + newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]); + } keysRequest = new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString); } diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 6687e784f01..f3edbf1f466 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -4,6 +4,7 @@ import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -36,6 +37,7 @@ export class ShareComponent implements OnInit, OnDestroy { protected cipherService: CipherService, private logService: LogService, protected organizationService: OrganizationService, + protected accountService: AccountService, ) {} async ngOnInit() { @@ -67,8 +69,11 @@ export class ShareComponent implements OnInit, OnDestroy { }); const cipherDomain = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); } @@ -95,8 +100,11 @@ export class ShareComponent implements OnInit, OnDestroy { } const cipherDomain = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipherView = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); const orgs = await firstValueFrom(this.organizations$); const orgName = @@ -104,7 +112,7 @@ export class ShareComponent implements OnInit, OnDestroy { try { this.formPromise = this.cipherService - .shareWithServer(cipherView, this.organizationId, selectedCollectionIds) + .shareWithServer(cipherView, this.organizationId, selectedCollectionIds, activeUserId) .then(async () => { this.onSharedCipher.emit(); this.platformUtilsService.showToast( diff --git a/libs/angular/src/platform/guard/feature-flag.guard.spec.ts b/libs/angular/src/platform/guard/feature-flag.guard.spec.ts index 323e8c26590..d39e071a693 100644 --- a/libs/angular/src/platform/guard/feature-flag.guard.spec.ts +++ b/libs/angular/src/platform/guard/feature-flag.guard.spec.ts @@ -8,8 +8,7 @@ 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 { I18nMockService } from "@bitwarden/components/src"; +import { I18nMockService, ToastService } from "@bitwarden/components/src"; import { canAccessFeature } from "./feature-flag.guard"; @@ -22,11 +21,11 @@ describe("canAccessFeature", () => { const redirectRoute = "redirect"; let mockConfigService: MockProxy; - let mockPlatformUtilsService: MockProxy; + let mockToastService: MockProxy; const setup = (featureGuard: CanActivateFn, flagValue: any) => { mockConfigService = mock(); - mockPlatformUtilsService = mock(); + mockToastService = mock(); // Mock the correct getter based on the type of flagValue; also mock default values if one is not provided if (typeof flagValue === "boolean") { @@ -57,7 +56,7 @@ describe("canAccessFeature", () => { ], providers: [ { provide: ConfigService, useValue: mockConfigService }, - { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, + { provide: ToastService, useValue: mockToastService }, { provide: LogService, useValue: mock() }, { provide: I18nService, @@ -117,11 +116,11 @@ describe("canAccessFeature", () => { await router.navigate([featureRoute]); - expect(mockPlatformUtilsService.showToast).toHaveBeenCalledWith( - "error", - null, - "Access Denied!", - ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: null, + message: "Access Denied!", + }); }); it("does not show an error toast when the feature flag is enabled", async () => { @@ -129,7 +128,7 @@ describe("canAccessFeature", () => { await router.navigate([featureRoute]); - expect(mockPlatformUtilsService.showToast).not.toHaveBeenCalled(); + expect(mockToastService.showToast).not.toHaveBeenCalled(); }); it("redirects to the specified redirect url when the feature flag is disabled", async () => { diff --git a/libs/angular/src/platform/guard/feature-flag.guard.ts b/libs/angular/src/platform/guard/feature-flag.guard.ts index bfcabc2b53c..1f82d810e36 100644 --- a/libs/angular/src/platform/guard/feature-flag.guard.ts +++ b/libs/angular/src/platform/guard/feature-flag.guard.ts @@ -5,7 +5,7 @@ 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 { ToastService } from "@bitwarden/components"; // Replace this with a type safe lookup of the feature flag values in PM-2282 type FlagValue = boolean | number | string; @@ -24,7 +24,7 @@ export const canAccessFeature = ( ): CanActivateFn => { return async () => { const configService = inject(ConfigService); - const platformUtilsService = inject(PlatformUtilsService); + const toastService = inject(ToastService); const router = inject(Router); const i18nService = inject(I18nService); const logService = inject(LogService); @@ -36,7 +36,11 @@ export const canAccessFeature = ( return true; } - platformUtilsService.showToast("error", null, i18nService.t("accessDenied")); + toastService.showToast({ + variant: "error", + title: null, + message: i18nService.t("accessDenied"), + }); if (redirectUrlOnDisabled != null) { return router.createUrlTree([redirectUrlOnDisabled]); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index c8b6011c815..0997fb68635 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -123,14 +123,12 @@ import { BillingApiServiceAbstraction, BraintreeServiceAbstraction, OrganizationBillingServiceAbstraction, - PaymentMethodWarningsServiceAbstraction, StripeServiceAbstraction, } from "@bitwarden/common/billing/abstractions"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; -import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services/payment-method-warnings.service"; import { BraintreeService } from "@bitwarden/common/billing/services/payment-processors/braintree.service"; import { StripeService } from "@bitwarden/common/billing/services/payment-processors/stripe.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; @@ -775,6 +773,7 @@ const safeProviders: SafeProvider[] = [ CollectionServiceAbstraction, CryptoServiceAbstraction, PinServiceAbstraction, + AccountServiceAbstraction, ], }), safeProvider({ @@ -800,6 +799,7 @@ const safeProviders: SafeProvider[] = [ CryptoFunctionServiceAbstraction, CollectionServiceAbstraction, KdfConfigServiceAbstraction, + AccountServiceAbstraction, ], }), safeProvider({ @@ -1199,11 +1199,6 @@ const safeProviders: SafeProvider[] = [ useClass: BillingApiService, deps: [ApiServiceAbstraction, LogService, ToastService], }), - safeProvider({ - provide: PaymentMethodWarningsServiceAbstraction, - useClass: PaymentMethodWarningsService, - deps: [BillingApiServiceAbstraction, StateProvider], - }), safeProvider({ provide: BillingAccountProfileStateService, useClass: DefaultBillingAccountProfileStateService, diff --git a/libs/angular/src/utils/extension-refresh-swap.ts b/libs/angular/src/utils/extension-refresh-swap.ts new file mode 100644 index 00000000000..6512be032d2 --- /dev/null +++ b/libs/angular/src/utils/extension-refresh-swap.ts @@ -0,0 +1,32 @@ +import { Type, inject } from "@angular/core"; +import { Route, Routes } from "@angular/router"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { componentRouteSwap } from "./component-route-swap"; + +/** + * Helper function to swap between two components based on the ExtensionRefresh feature flag. + * @param defaultComponent - The current non-refreshed component to render. + * @param refreshedComponent - The new refreshed component to render. + * @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided. + * @param altOptions - The alt route options to apply to the alt component. + */ +export function extensionRefreshSwap( + defaultComponent: Type, + refreshedComponent: Type, + options: Route, + altOptions?: Route, +): Routes { + return componentRouteSwap( + defaultComponent, + refreshedComponent, + async () => { + const configService = inject(ConfigService); + return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh); + }, + options, + altOptions, + ); +} diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 38a4c8fe483..909a905e9b0 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -22,6 +22,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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -250,8 +251,11 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); // Adjust Cipher Name if Cloning @@ -371,7 +375,10 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.id = null; } - const cipher = await this.encryptCipher(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher = await this.encryptCipher(activeUserId); try { this.formPromise = this.saveCipher(cipher); await this.formPromise; @@ -664,8 +671,8 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.cipherService.get(this.cipherId); } - protected encryptCipher() { - return this.cipherService.encrypt(this.cipher); + protected encryptCipher(userId: UserId) { + return this.cipherService.encrypt(this.cipher, userId); } protected saveCipher(cipher: Cipher) { diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 68b336a8b06..e377427eb89 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -1,7 +1,8 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -11,6 +12,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 { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; @@ -46,6 +48,7 @@ export class AttachmentsComponent implements OnInit { protected fileDownloadService: FileDownloadService, protected dialogService: DialogService, protected billingAccountProfileStateService: BillingAccountProfileStateService, + protected accountService: AccountService, ) {} async ngOnInit() { @@ -75,10 +78,13 @@ export class AttachmentsComponent implements OnInit { } try { - this.formPromise = this.saveCipherAttachment(files[0]); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved")); this.onUploadedAttachment.emit(); @@ -185,8 +191,11 @@ export class AttachmentsComponent implements OnInit { protected async init() { this.cipherDomain = await this.loadCipher(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); const canAccessPremium = await firstValueFrom( @@ -235,14 +244,18 @@ export class AttachmentsComponent implements OnInit { ? attachment.key : await this.cryptoService.getOrgKey(this.cipher.organizationId); const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, attachment.fileName, decBuf, + activeUserId, admin, ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); // 3. Delete old @@ -278,8 +291,8 @@ export class AttachmentsComponent implements OnInit { return this.cipherService.get(this.cipherId); } - protected saveCipherAttachment(file: File) { - return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file); + protected saveCipherAttachment(file: File, userId: UserId) { + return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId); } protected deleteCipherAttachment(attachmentId: string) { diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 6d8b4015cb0..e2784620a26 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -1,5 +1,7 @@ import { Directive, OnInit } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -14,6 +16,7 @@ export class PasswordHistoryComponent implements OnInit { protected cipherService: CipherService, protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, + protected accountService: AccountService, private win: Window, ) {} @@ -33,8 +36,11 @@ export class PasswordHistoryComponent implements OnInit { protected async init() { const cipher = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 27d6e14b118..a6e96bc542a 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -9,11 +9,12 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; @@ -100,6 +101,7 @@ export class ViewComponent implements OnDestroy, OnInit { protected fileDownloadService: FileDownloadService, protected dialogService: DialogService, protected datePipe: DatePipe, + protected accountService: AccountService, private billingAccountProfileStateService: BillingAccountProfileStateService, ) {} @@ -129,8 +131,11 @@ export class ViewComponent implements OnDestroy, OnInit { this.cleanUp(); const cipher = await this.cipherService.get(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.canAccessPremium = await firstValueFrom( this.billingAccountProfileStateService.hasPremiumFromAnySource$, diff --git a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts index 83db2fcd878..fd7feab4c32 100644 --- a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts @@ -8,7 +8,6 @@ import { PaymentInformationResponse } from "@bitwarden/common/billing/models/res import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; -import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; @@ -34,8 +33,6 @@ export abstract class BillingApiServiceAbstraction { organizationId: string, ) => Promise; - getOrganizationBillingStatus: (id: string) => Promise; - getPlans: () => Promise>; getProviderClientInvoiceReport: (providerId: string, invoiceId: string) => Promise; diff --git a/libs/common/src/billing/abstractions/index.ts b/libs/common/src/billing/abstractions/index.ts index 08a7a28fd9c..c3ef8baca29 100644 --- a/libs/common/src/billing/abstractions/index.ts +++ b/libs/common/src/billing/abstractions/index.ts @@ -1,7 +1,6 @@ export * from "./account/billing-account-profile-state.service"; export * from "./billilng-api.service.abstraction"; export * from "./organization-billing.service"; -export * from "./payment-method-warnings-service.abstraction"; export * from "./payment-processors/braintree.service.abstraction"; export * from "./payment-processors/stripe.service.abstraction"; export * from "./provider-billing.service.abstraction"; diff --git a/libs/common/src/billing/abstractions/payment-method-warnings-service.abstraction.ts b/libs/common/src/billing/abstractions/payment-method-warnings-service.abstraction.ts deleted file mode 100644 index d7ba522c602..00000000000 --- a/libs/common/src/billing/abstractions/payment-method-warnings-service.abstraction.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Observable } from "rxjs"; - -import { PaymentMethodWarning } from "../models/domain/payment-method-warning"; - -export abstract class PaymentMethodWarningsServiceAbstraction { - /** - * An {@link Observable} record in the {@link ActiveUserState} of the user's organization IDs each mapped to their respective {@link PaymentMethodWarning}. - */ - paymentMethodWarnings$: Observable>; - /** - * Updates the {@link ActiveUserState} by setting `acknowledged` to `true` for the {@link PaymentMethodWarning} represented by the provided organization ID. - * @param organizationId - The ID of the organization whose warning you'd like to acknowledge. - */ - acknowledge: (organizationId: string) => Promise; - /** - * Updates the {@link ActiveUserState} by setting `risksSubscriptionFailure` to `false` for the {@link PaymentMethodWarning} represented by the provided organization ID. - * @param organizationId - The ID of the organization whose subscription risk you'd like to remove. - */ - removeSubscriptionRisk: (organizationId: string) => Promise; - /** - * Clears the {@link PaymentMethodWarning} record from the {@link ActiveUserState}. - */ - clear: () => Promise; - /** - * Tries to retrieve the {@link PaymentMethodWarning} for the provided organization ID from the {@link ActiveUserState}. - * If the warning does not exist, or if the warning has been in state for longer than a week, fetches the current {@link OrganizationBillingStatusResponse} for the organization - * from the API and uses it to update the warning in state. - * @param organizationId - The ID of the organization whose {@link PaymentMethodWarning} you'd like to update. - */ - update: (organizationId: string) => Promise; -} diff --git a/libs/common/src/billing/models/billing-keys.state.ts b/libs/common/src/billing/models/billing-keys.state.ts deleted file mode 100644 index 1d1cce6d0b2..00000000000 --- a/libs/common/src/billing/models/billing-keys.state.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { BILLING_DISK, UserKeyDefinition } from "../../platform/state"; -import { PaymentMethodWarning } from "../models/domain/payment-method-warning"; - -export const PAYMENT_METHOD_WARNINGS_KEY = UserKeyDefinition.record( - BILLING_DISK, - "paymentMethodWarnings", - { - deserializer: (warnings) => ({ - ...warnings, - savedAt: new Date(warnings.savedAt), - }), - clearOn: ["logout"], - }, -); diff --git a/libs/common/src/billing/models/domain/index.ts b/libs/common/src/billing/models/domain/index.ts index 19be9c1aeb2..66d7e29c100 100644 --- a/libs/common/src/billing/models/domain/index.ts +++ b/libs/common/src/billing/models/domain/index.ts @@ -1,5 +1,4 @@ export * from "./bank-account"; export * from "./masked-payment-method"; -export * from "./payment-method-warning"; export * from "./tax-information"; export * from "./tokenized-payment-method"; diff --git a/libs/common/src/billing/models/domain/payment-method-warning.ts b/libs/common/src/billing/models/domain/payment-method-warning.ts deleted file mode 100644 index dd3f0f85810..00000000000 --- a/libs/common/src/billing/models/domain/payment-method-warning.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type PaymentMethodWarning = { - organizationName: string; - risksSubscriptionFailure: boolean; - acknowledged: boolean; - savedAt: Date; -}; diff --git a/libs/common/src/billing/models/response/organization-billing-status.response.ts b/libs/common/src/billing/models/response/organization-billing-status.response.ts deleted file mode 100644 index 916bebe4bca..00000000000 --- a/libs/common/src/billing/models/response/organization-billing-status.response.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BaseResponse } from "../../../models/response/base.response"; - -export class OrganizationBillingStatusResponse extends BaseResponse { - organizationId: string; - organizationName: string; - risksSubscriptionFailure: boolean; - - constructor(response: any) { - super(response); - - this.organizationId = this.getResponseProperty("OrganizationId"); - this.organizationName = this.getResponseProperty("OrganizationName"); - this.risksSubscriptionFailure = this.getResponseProperty("RisksSubscriptionFailure"); - } -} diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index a5841fc5b55..822e6d16871 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -12,7 +12,6 @@ import { SubscriptionCancellationRequest } from "../../billing/models/request/su import { TokenizedPaymentMethodRequest } from "../../billing/models/request/tokenized-payment-method.request"; import { VerifyBankAccountRequest } from "../../billing/models/request/verify-bank-account.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; -import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; import { PaymentInformationResponse } from "../../billing/models/response/payment-information.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; @@ -72,17 +71,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { return response as string; } - async getOrganizationBillingStatus(id: string): Promise { - const r = await this.apiService.send( - "GET", - "/organizations/" + id + "/billing-status", - null, - true, - true, - ); - return new OrganizationBillingStatusResponse(r); - } - async getOrganizationBillingMetadata( organizationId: string, ): Promise { diff --git a/libs/common/src/billing/services/payment-method-warnings.service.spec.ts b/libs/common/src/billing/services/payment-method-warnings.service.spec.ts deleted file mode 100644 index 6e37821ef50..00000000000 --- a/libs/common/src/billing/services/payment-method-warnings.service.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { any, mock, MockProxy } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; - -import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec"; -import { FakeActiveUserState } from "../../../spec/fake-state"; -import { Utils } from "../../platform/misc/utils"; -import { UserId } from "../../types/guid"; -import { BillingApiServiceAbstraction as BillingApiService } from "../abstractions/billilng-api.service.abstraction"; -import { PAYMENT_METHOD_WARNINGS_KEY } from "../models/billing-keys.state"; -import { PaymentMethodWarning } from "../models/domain/payment-method-warning"; -import { OrganizationBillingStatusResponse } from "../models/response/organization-billing-status.response"; - -import { PaymentMethodWarningsService } from "./payment-method-warnings.service"; - -describe("Payment Method Warnings Service", () => { - let paymentMethodWarningsService: PaymentMethodWarningsService; - let billingApiService: MockProxy; - - const mockUserId = Utils.newGuid() as UserId; - let accountService: FakeAccountService; - let stateProvider: FakeStateProvider; - let activeUserState: FakeActiveUserState>; - - function getPastDate(daysAgo: number) { - const date = new Date(); - date.setDate(date.getDate() - daysAgo); - return date; - } - - const getBillingStatusResponse = (organizationId: string) => - new OrganizationBillingStatusResponse({ - OrganizationId: organizationId, - OrganizationName: "Teams Organization", - RisksSubscriptionFailure: true, - }); - - beforeEach(() => { - accountService = mockAccountServiceWith(mockUserId); - stateProvider = new FakeStateProvider(accountService); - activeUserState = stateProvider.activeUser.getFake(PAYMENT_METHOD_WARNINGS_KEY); - - billingApiService = mock(); - paymentMethodWarningsService = new PaymentMethodWarningsService( - billingApiService, - stateProvider, - ); - }); - - it("acknowledge", async () => { - const organizationId = "1"; - const state: Record = { - [organizationId]: { - organizationName: "Teams Organization", - risksSubscriptionFailure: true, - acknowledged: false, - savedAt: getPastDate(3), - }, - }; - activeUserState.nextState(state); - await paymentMethodWarningsService.acknowledge(organizationId); - expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({ - [organizationId]: { - ...state[organizationId], - acknowledged: true, - }, - }); - }); - - it("clear", async () => { - const state: Record = { - "1": { - organizationName: "Teams Organization", - risksSubscriptionFailure: true, - acknowledged: false, - savedAt: getPastDate(3), - }, - }; - activeUserState.nextState(state); - await paymentMethodWarningsService.clear(); - expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({}); - }); - - it("removeSubscriptionRisk", async () => { - const organizationId = "1"; - const state: Record = { - [organizationId]: { - organizationName: "Teams Organization", - risksSubscriptionFailure: true, - acknowledged: false, - savedAt: getPastDate(3), - }, - }; - activeUserState.nextState(state); - await paymentMethodWarningsService.removeSubscriptionRisk(organizationId); - expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({ - [organizationId]: { - ...state[organizationId], - risksSubscriptionFailure: false, - }, - }); - }); - - describe("update", () => { - it("Does nothing if the stored payment method warning is less than a week old", async () => { - const organizationId = "1"; - const state: Record = { - [organizationId]: { - organizationName: "Teams Organization", - risksSubscriptionFailure: true, - acknowledged: false, - savedAt: getPastDate(3), - }, - }; - activeUserState.nextState(state); - await paymentMethodWarningsService.update(organizationId); - expect(billingApiService.getOrganizationBillingStatus).not.toHaveBeenCalled(); - }); - - it("Retrieves the billing status from the API and uses it to update the state if the state is null", async () => { - const organizationId = "1"; - activeUserState.nextState(null); - billingApiService.getOrganizationBillingStatus.mockResolvedValue( - getBillingStatusResponse(organizationId), - ); - await paymentMethodWarningsService.update(organizationId); - expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({ - [organizationId]: { - organizationName: "Teams Organization", - risksSubscriptionFailure: true, - acknowledged: false, - savedAt: any(), - }, - }); - expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1); - }); - - it("Retrieves the billing status from the API and uses it to update the state if the stored warning is null", async () => { - const organizationId = "1"; - activeUserState.nextState({ - [organizationId]: null, - }); - billingApiService.getOrganizationBillingStatus.mockResolvedValue( - getBillingStatusResponse(organizationId), - ); - await paymentMethodWarningsService.update(organizationId); - expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({ - [organizationId]: { - organizationName: "Teams Organization", - risksSubscriptionFailure: true, - acknowledged: false, - savedAt: any(), - }, - }); - expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1); - }); - - it("Retrieves the billing status from the API and uses it to update the state if the stored warning is older than a week", async () => { - const organizationId = "1"; - activeUserState.nextState({ - [organizationId]: { - organizationName: "Teams Organization", - risksSubscriptionFailure: false, - acknowledged: false, - savedAt: getPastDate(10), - }, - }); - billingApiService.getOrganizationBillingStatus.mockResolvedValue( - new OrganizationBillingStatusResponse({ - OrganizationId: organizationId, - OrganizationName: "Teams Organization", - RisksSubscriptionFailure: true, - }), - ); - await paymentMethodWarningsService.update(organizationId); - expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({ - [organizationId]: { - organizationName: "Teams Organization", - risksSubscriptionFailure: true, - acknowledged: false, - savedAt: any(), - }, - }); - expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/libs/common/src/billing/services/payment-method-warnings.service.ts b/libs/common/src/billing/services/payment-method-warnings.service.ts deleted file mode 100644 index 0dad48bb85f..00000000000 --- a/libs/common/src/billing/services/payment-method-warnings.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { firstValueFrom, map, Observable } from "rxjs"; - -import { ActiveUserState, StateProvider } from "../../platform/state"; -import { BillingApiServiceAbstraction as BillingApiService } from "../abstractions/billilng-api.service.abstraction"; -import { PaymentMethodWarningsServiceAbstraction } from "../abstractions/payment-method-warnings-service.abstraction"; -import { PAYMENT_METHOD_WARNINGS_KEY } from "../models/billing-keys.state"; -import { PaymentMethodWarning } from "../models/domain/payment-method-warning"; - -export class PaymentMethodWarningsService implements PaymentMethodWarningsServiceAbstraction { - private paymentMethodWarningsState: ActiveUserState>; - paymentMethodWarnings$: Observable>; - - constructor( - private billingApiService: BillingApiService, - private stateProvider: StateProvider, - ) { - this.paymentMethodWarningsState = this.stateProvider.getActive(PAYMENT_METHOD_WARNINGS_KEY); - this.paymentMethodWarnings$ = this.paymentMethodWarningsState.state$; - } - - async acknowledge(organizationId: string): Promise { - await this.paymentMethodWarningsState.update((state) => { - const current = state[organizationId]; - state[organizationId] = { - ...current, - acknowledged: true, - }; - return state; - }); - } - - async removeSubscriptionRisk(organizationId: string): Promise { - await this.paymentMethodWarningsState.update((state) => { - const current = state[organizationId]; - state[organizationId] = { - ...current, - risksSubscriptionFailure: false, - }; - return state; - }); - } - - async clear(): Promise { - await this.paymentMethodWarningsState.update(() => ({})); - } - - async update(organizationId: string): Promise { - const warning = await firstValueFrom( - this.paymentMethodWarningsState.state$.pipe( - map((state) => (!state ? null : state[organizationId])), - ), - ); - if (!warning || warning.savedAt < this.getOneWeekAgo()) { - const { organizationName, risksSubscriptionFailure } = - await this.billingApiService.getOrganizationBillingStatus(organizationId); - await this.paymentMethodWarningsState.update((state) => { - state ??= {}; - state[organizationId] = { - organizationName, - risksSubscriptionFailure, - acknowledged: false, - savedAt: new Date(), - }; - return state; - }); - } - } - - private getOneWeekAgo = (): Date => { - const date = new Date(); - date.setDate(date.getDate() - 7); - return date; - }; -} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 583b6996970..9edc14e7b09 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -7,7 +7,6 @@ export enum FeatureFlag { BrowserFilelessImport = "browser-fileless-import", ItemShare = "item-share", GeneratorToolsModernization = "generator-tools-modernization", - ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners", EnableConsolidatedBilling = "enable-consolidated-billing", AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section", EnableDeleteProvider = "AC-1218-delete-provider", @@ -50,7 +49,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.BrowserFilelessImport]: FALSE, [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.GeneratorToolsModernization]: FALSE, - [FeatureFlag.ShowPaymentMethodWarningBanners]: FALSE, [FeatureFlag.EnableConsolidatedBilling]: FALSE, [FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE, [FeatureFlag.EnableDeleteProvider]: FALSE, diff --git a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts index 9882febdd39..55232162d47 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts @@ -40,6 +40,11 @@ export interface PickCredentialParams { * Whether or not the user must be verified before completing the operation. */ userVerification: boolean; + + /** + * Bypass the UI and assume that the user has already interacted with the authenticator. + */ + assumeUserPresence?: boolean; } /** diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 806b6592737..adb1adcce48 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -1,7 +1,10 @@ import { TextEncoder } from "util"; import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; +import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; +import { UserId } from "../../../types/guid"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type"; @@ -30,10 +33,18 @@ import { guidToRawFormat } from "./guid-utils"; const RpId = "bitwarden.com"; describe("FidoAuthenticatorService", () => { + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + let cipherService!: MockProxy; let userInterface!: MockProxy; let userInterfaceSession!: MockProxy; let syncService!: MockProxy; + let accountService!: MockProxy; let authenticator!: Fido2AuthenticatorService; let tab!: chrome.tabs.Tab; @@ -43,8 +54,15 @@ describe("FidoAuthenticatorService", () => { userInterfaceSession = mock(); userInterface.newSession.mockResolvedValue(userInterfaceSession); syncService = mock(); - authenticator = new Fido2AuthenticatorService(cipherService, userInterface, syncService); + accountService = mock(); + authenticator = new Fido2AuthenticatorService( + cipherService, + userInterface, + syncService, + accountService, + ); tab = { id: 123, windowId: 456 } as chrome.tabs.Tab; + accountService.activeAccount$ = activeAccountSubject; }); describe("makeCredential", () => { @@ -677,6 +695,7 @@ describe("FidoAuthenticatorService", () => { ], }), }), + "testId", ); }); diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 70907f98656..ef09a3d1604 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -1,3 +1,6 @@ +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "../../../auth/abstractions/account.service"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type"; @@ -42,6 +45,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr private cipherService: CipherService, private userInterface: Fido2UserInterfaceService, private syncService: SyncService, + private accountService: AccountService, private logService?: LogService, ) {} @@ -130,8 +134,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr keyPair = await createKeyPair(); pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey); const encrypted = await this.cipherService.get(cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + cipher = await encrypted.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(encrypted), + await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId), ); if ( @@ -150,7 +158,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr if (Utils.isNullOrEmpty(cipher.login.username)) { cipher.login.username = fido2Credential.userName; } - const reencrypted = await this.cipherService.encrypt(cipher); + const reencrypted = await this.cipherService.encrypt(cipher, activeUserId); await this.cipherService.updateWithServer(reencrypted); credentialId = fido2Credential.credentialId; } catch (error) { @@ -235,10 +243,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr } let response = { cipherId: cipherOptions[0].id, userVerified: false }; + if (this.requiresUserVerificationPrompt(params, cipherOptions)) { response = await userInterfaceSession.pickCredential({ cipherIds: cipherOptions.map((cipher) => cipher.id), userVerification: params.requireUserVerification, + assumeUserPresence: params.assumeUserPresence, }); } @@ -277,7 +287,10 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr }; if (selectedFido2Credential.counter > 0) { - const encrypted = await this.cipherService.encrypt(selectedCipher); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId); await this.cipherService.updateWithServer(encrypted); } diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index fd028df09b0..e9cfa37d118 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -1,4 +1,4 @@ -import { firstValueFrom, map, of, switchMap } from "rxjs"; +import { firstValueFrom, map, Observable, of, switchMap } from "rxjs"; import { ApiService } from "../../abstractions/api.service"; import { AccountService } from "../../auth/abstractions/account.service"; @@ -67,6 +67,17 @@ export abstract class CoreSyncService implements SyncService { return this.stateProvider.getUser(userId, LAST_SYNC_DATE).state$; } + activeUserLastSync$(): Observable { + return this.accountService.activeAccount$.pipe( + switchMap((a) => { + if (a == null) { + return of(null); + } + return this.lastSync$(a.id); + }), + ); + } + async setLastSync(date: Date, userId: UserId): Promise { await this.stateProvider.getUser(userId, LAST_SYNC_DATE).update(() => date); } diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 6877f5a1ca5..e48ab0618c3 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -175,7 +175,7 @@ export class DefaultSyncService extends CoreSyncService { throw new Error("Stamp has changed"); } - await this.cryptoService.setMasterKeyEncryptedUserKey(response.key); + await this.cryptoService.setMasterKeyEncryptedUserKey(response.key, response.id); await this.cryptoService.setPrivateKey(response.privateKey, response.id); await this.cryptoService.setProviderKeys(response.providers, response.id); await this.cryptoService.setOrgKeys( diff --git a/libs/common/src/platform/sync/sync.service.ts b/libs/common/src/platform/sync/sync.service.ts index be5aa4622c6..733b7beaff5 100644 --- a/libs/common/src/platform/sync/sync.service.ts +++ b/libs/common/src/platform/sync/sync.service.ts @@ -34,6 +34,12 @@ export abstract class SyncService { */ abstract lastSync$(userId: UserId): Observable; + /** + * Retrieves a stream of the currently active user's last sync date. + * Or null if there is no current active user or the active user has not synced before. + */ + abstract activeUserLastSync$(): Observable; + /** * Optionally does a full sync operation including going to the server to gather the source * of truth and set that data to state. diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 83bdf016ed1..c95ae27f612 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -27,6 +27,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; encrypt: ( model: CipherView, + userId: UserId, keyForEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher?: Cipher, @@ -83,21 +84,25 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; shareManyWithServer: ( ciphers: CipherView[], organizationId: string, collectionIds: string[], + userId: UserId, ) => Promise; saveAttachmentWithServer: ( cipher: Cipher, unencryptedFile: any, + userId: UserId, admin?: boolean, ) => Promise; saveAttachmentRawWithServer: ( cipher: Cipher, filename: string, data: ArrayBuffer, + userId: UserId, admin?: boolean, ) => Promise; /** @@ -147,7 +152,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; restoreWithServer: (id: string, asAdmin?: boolean) => Promise; restoreManyWithServer: (ids: string[], orgId?: string) => Promise; - getKeyForCipherKeyDecryption: (cipher: Cipher) => Promise; + getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise; setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise; /** * Returns user ciphers re-encrypted with the new user key. diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index a75d645831f..f10884b55ae 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; +import { UserId } from "@bitwarden/common/types/guid"; + import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; import { CryptoService } from "../../../platform/abstractions/crypto.service"; @@ -247,7 +249,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -367,7 +369,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -505,7 +507,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -667,7 +669,7 @@ describe("Cipher DTO", () => { ); const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher), + await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), ); expect(cipherView).toMatchObject({ @@ -754,3 +756,5 @@ describe("Cipher DTO", () => { }); }); }); + +const mockUserId = "TestUserId" as UserId; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index e3019ab48d2..b6b35f1886a 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -121,6 +121,8 @@ describe("Cipher Service", () => { accountService = mockAccountServiceWith(mockUserId); const stateProvider = new FakeStateProvider(accountService); + const userId = "TestUserId" as UserId; + let cipherService: CipherService; let cipherObj: Cipher; @@ -168,7 +170,7 @@ describe("Cipher Service", () => { const spy = jest.spyOn(cipherFileUploadService, "upload"); - await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData); + await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData, userId); expect(spy).toHaveBeenCalled(); }); @@ -283,7 +285,7 @@ describe("Cipher Service", () => { { uri: "uri", match: UriMatchStrategy.RegularExpression } as LoginUriView, ]; - const domain = await cipherService.encrypt(cipherView); + const domain = await cipherService.encrypt(cipherView, userId); expect(domain.login.uris).toEqual([ { @@ -299,7 +301,7 @@ describe("Cipher Service", () => { it("is null when enableCipherKeyEncryption flag is false", async () => { setEncryptionKeyFlag(false); - const cipher = await cipherService.encrypt(cipherView); + const cipher = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeNull(); }); @@ -307,7 +309,7 @@ describe("Cipher Service", () => { it("is defined when enableCipherKeyEncryption flag is true", async () => { setEncryptionKeyFlag(true); - const cipher = await cipherService.encrypt(cipherView); + const cipher = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeDefined(); }); @@ -321,7 +323,7 @@ describe("Cipher Service", () => { it("is not called when enableCipherKeyEncryption is false", async () => { setEncryptionKeyFlag(false); - await cipherService.encrypt(cipherView); + await cipherService.encrypt(cipherView, userId); expect(cipherService["encryptCipherWithCipherKey"]).not.toHaveBeenCalled(); }); @@ -329,7 +331,7 @@ describe("Cipher Service", () => { it("is called when enableCipherKeyEncryption is true", async () => { setEncryptionKeyFlag(true); - await cipherService.encrypt(cipherView); + await cipherService.encrypt(cipherView, userId); expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled(); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 3eeac75db09..92676aea97b 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -165,6 +165,7 @@ export class CipherService implements CipherServiceAbstraction { async encrypt( model: CipherView, + userId: UserId, keyForEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher: Cipher = null, @@ -174,7 +175,7 @@ export class CipherService implements CipherServiceAbstraction { originalCipher = await this.get(model.id); } if (originalCipher != null) { - await this.updateModelfromExistingCipher(model, originalCipher); + await this.updateModelfromExistingCipher(model, originalCipher, userId); } this.adjustPasswordHistoryLength(model); } @@ -192,7 +193,7 @@ export class CipherService implements CipherServiceAbstraction { if (await this.getCipherKeyEncryptionEnabled()) { cipher.key = originalCipher?.key ?? null; - const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher); + const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher, userId); // The keyForEncryption is only used for encrypting the cipher key, not the cipher itself, since cipher key encryption is enabled. // If the caller has provided a key for cipher key encryption, use it. Otherwise, use the user or org key. keyForEncryption ||= userOrOrgKey; @@ -718,6 +719,7 @@ export class CipherService implements CipherServiceAbstraction { cipher: CipherView, organizationId: string, collectionIds: string[], + userId: UserId, ): Promise { const attachmentPromises: Promise[] = []; if (cipher.attachments != null) { @@ -733,7 +735,7 @@ export class CipherService implements CipherServiceAbstraction { cipher.organizationId = organizationId; cipher.collectionIds = collectionIds; - const encCipher = await this.encryptSharedCipher(cipher); + const encCipher = await this.encryptSharedCipher(cipher, userId); const request = new CipherShareRequest(encCipher); const response = await this.apiService.putShareCipher(cipher.id, request); const data = new CipherData(response, collectionIds); @@ -744,6 +746,7 @@ export class CipherService implements CipherServiceAbstraction { ciphers: CipherView[], organizationId: string, collectionIds: string[], + userId: UserId, ): Promise { const promises: Promise[] = []; const encCiphers: Cipher[] = []; @@ -751,7 +754,7 @@ export class CipherService implements CipherServiceAbstraction { cipher.organizationId = organizationId; cipher.collectionIds = collectionIds; promises.push( - this.encryptSharedCipher(cipher).then((c) => { + this.encryptSharedCipher(cipher, userId).then((c) => { encCiphers.push(c); }), ); @@ -770,7 +773,12 @@ export class CipherService implements CipherServiceAbstraction { await this.upsert(encCiphers.map((c) => c.toCipherData())); } - saveAttachmentWithServer(cipher: Cipher, unencryptedFile: any, admin = false): Promise { + saveAttachmentWithServer( + cipher: Cipher, + unencryptedFile: any, + userId: UserId, + admin = false, + ): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsArrayBuffer(unencryptedFile); @@ -780,6 +788,7 @@ export class CipherService implements CipherServiceAbstraction { cipher, unencryptedFile.name, evt.target.result, + userId, admin, ); resolve(cData); @@ -797,9 +806,10 @@ export class CipherService implements CipherServiceAbstraction { cipher: Cipher, filename: string, data: Uint8Array, + userId: UserId, admin = false, ): Promise { - const encKey = await this.getKeyForCipherKeyDecryption(cipher); + const encKey = await this.getKeyForCipherKeyDecryption(cipher, userId); const cipherKeyEncryptionEnabled = await this.getCipherKeyEncryptionEnabled(); const cipherEncKey = @@ -813,8 +823,8 @@ export class CipherService implements CipherServiceAbstraction { //then we rollback to using the user key as the main key of encryption of the item //in order to keep item and it's attachments with the same encryption level if (cipher.key != null && !cipherKeyEncryptionEnabled) { - const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher)); - cipher = await this.encrypt(model); + const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher, userId)); + cipher = await this.encrypt(model, userId); await this.updateWithServer(cipher); } @@ -1209,10 +1219,10 @@ export class CipherService implements CipherServiceAbstraction { await this.restore(restores); } - async getKeyForCipherKeyDecryption(cipher: Cipher): Promise { + async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise { return ( (await this.cryptoService.getOrgKey(cipher.organizationId)) || - ((await this.cryptoService.getUserKeyWithLegacySupport()) as UserKey) + ((await this.cryptoService.getUserKeyWithLegacySupport(userId)) as UserKey) ); } @@ -1247,7 +1257,7 @@ export class CipherService implements CipherServiceAbstraction { } encryptedCiphers = await Promise.all( userCiphers.map(async (cipher) => { - const encryptedCipher = await this.encrypt(cipher, newUserKey, originalUserKey); + const encryptedCipher = await this.encrypt(cipher, userId, newUserKey, originalUserKey); return new CipherWithIdRequest(encryptedCipher); }), ); @@ -1259,17 +1269,18 @@ export class CipherService implements CipherServiceAbstraction { // In the case of a cipher that is being shared with an organization, we want to decrypt the // cipher key with the user's key and then re-encrypt it with the organization's key. - private async encryptSharedCipher(model: CipherView): Promise { - const keyForCipherKeyDecryption = await this.cryptoService.getUserKeyWithLegacySupport(); - return await this.encrypt(model, null, keyForCipherKeyDecryption); + private async encryptSharedCipher(model: CipherView, userId: UserId): Promise { + const keyForCipherKeyDecryption = await this.cryptoService.getUserKeyWithLegacySupport(userId); + return await this.encrypt(model, userId, null, keyForCipherKeyDecryption); } private async updateModelfromExistingCipher( model: CipherView, originalCipher: Cipher, + userId: UserId, ): Promise { const existingCipher = await originalCipher.decrypt( - await this.getKeyForCipherKeyDecryption(originalCipher), + await this.getKeyForCipherKeyDecryption(originalCipher, userId), ); model.passwordHistory = existingCipher.passwordHistory || []; if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) { diff --git a/libs/components/src/dialog/dialogs.mdx b/libs/components/src/dialog/dialogs.mdx index a42e304c9a4..b5861aefe5c 100644 --- a/libs/components/src/dialog/dialogs.mdx +++ b/libs/components/src/dialog/dialogs.mdx @@ -1,5 +1,7 @@ import { Meta, Story, Source } from "@storybook/addon-docs"; +import * as stories from "./dialog.service.stories"; + ```ts @@ -28,7 +30,7 @@ dialog should become scrollable. A backdrop should be used to hide the content below the dialog. Use `#000000` with `30% opacity`. - + ## Accessibility diff --git a/libs/components/src/form/forms.mdx b/libs/components/src/form/forms.mdx index 6c6fa33f68e..dba6a1466c6 100644 --- a/libs/components/src/form/forms.mdx +++ b/libs/components/src/form/forms.mdx @@ -1,5 +1,14 @@ import { Meta, Story, Source } from "@storybook/addon-docs"; +import * as formStories from "./form.stories"; +import * as fieldStories from "../form-field/form-field.stories"; +import * as passwordToggleStories from "../form-field/password-input-toggle.stories"; +import * as searchStories from "../search/search.stories"; +import * as selectStories from "../select/select.stories"; +import * as multiSelectStories from "../form-field/multi-select.stories"; +import * as radioStories from "../radio-button/radio-button.stories"; +import * as checkboxStories from "../checkbox/checkbox.stories"; + # Forms @@ -8,9 +17,9 @@ Component Library forms should always be built using [Angular Reactive Forms][re [ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should always use the native `form` element and bind a `formGroup`. - + - +
## Form spacing and sections @@ -48,25 +57,25 @@ controls like email verification, number selection, and more. #### Default with required attribute - + #### Password Toggle - + -#### Search +### Search - + ### Selects #### Searchable single select (default) - + #### Multi-select - + ### Radio group @@ -89,14 +98,11 @@ using a radio group for more than 5 options even if the options require addition #### Block - + #### Inline - - -[reactive]: https://angular.io/guide/reactive-forms -[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms + ### Checkbox @@ -116,7 +122,7 @@ If a checkbox group has more than 4 options a #### Single checkbox - + ## Accessibility @@ -176,3 +182,6 @@ the field’s label. Maintain a ratio of 3:1 with the form's background. - Error styling should not rely only on using the `danger-600`color change. Use as a prefix to highlight the text as error text versus helper + +[reactive]: https://angular.io/guide/reactive-forms +[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms diff --git a/libs/components/src/item/index.ts b/libs/components/src/item/index.ts index 56896cdc3c6..3d65fdb8e05 100644 --- a/libs/components/src/item/index.ts +++ b/libs/components/src/item/index.ts @@ -1 +1,3 @@ export * from "./item.module"; + +export { BitItemHeight, BitItemHeightClass } from "./item.component"; diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 4b7b57fa9f3..58545a49b50 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -5,6 +5,20 @@ import { A11yRowDirective } from "../a11y/a11y-row.directive"; import { ItemActionComponent } from "./item-action.component"; +/** + * The class used to set the height of a bit item's inner content. + */ +export const BitItemHeightClass = `tw-h-[52px]`; + +/** + * The height of a bit item in pixels. Includes any margin, padding, or border. Used by the virtual scroll + * to estimate how many items can be displayed at once and how large the virtual container should be. + * Needs to be updated if the item height or spacing changes. + * + * 52px + 5.25px bottom margin + 1px border = 58.25px + */ +export const BitItemHeight = 58.25; // + @Component({ selector: "bit-item", standalone: true, diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index 58bd9716792..fa78f04d236 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -64,6 +64,7 @@ export default { skipToContent: "Skip to content", submenu: "submenu", toggleCollapse: "toggle collapse", + toggleSideNavigation: "toggle side navigation", }); }, }, diff --git a/libs/importer/spec/bitwarden-password-protected-importer.spec.ts b/libs/importer/spec/bitwarden-password-protected-importer.spec.ts index cfe5fa12c0f..d36ce8b9a64 100644 --- a/libs/importer/spec/bitwarden-password-protected-importer.spec.ts +++ b/libs/importer/spec/bitwarden-password-protected-importer.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KdfType } from "@bitwarden/common/platform/enums"; @@ -21,6 +22,7 @@ describe("BitwardenPasswordProtectedImporter", () => { let i18nService: MockProxy; let cipherService: MockProxy; let pinService: MockProxy; + let accountService: MockProxy; const password = Utils.newGuid(); const promptForPassword_callback = async () => { return password; @@ -31,12 +33,14 @@ describe("BitwardenPasswordProtectedImporter", () => { i18nService = mock(); cipherService = mock(); pinService = mock(); + accountService = mock(); importer = new BitwardenPasswordProtectedImporter( cryptoService, i18nService, cipherService, pinService, + accountService, promptForPassword_callback, ); }); diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 8e6229c7bf2..8ee882734b3 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -27,6 +27,7 @@ import { import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -89,6 +90,7 @@ const safeProviders: SafeProvider[] = [ CollectionService, CryptoService, PinServiceAbstraction, + AccountService, ], }), ]; diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index bef707c3e53..2248606814b 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -1,6 +1,7 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherWithIdExport, CollectionWithIdExport, @@ -33,6 +34,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { protected i18nService: I18nService, protected cipherService: CipherService, protected pinService: PinServiceAbstraction, + protected accountService: AccountService, ) { super(); } @@ -103,8 +105,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { }); } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const view = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.cleanupCipher(view); this.result.ciphers.push(view); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts index 83b5b78d62f..a854346bccb 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts @@ -1,4 +1,5 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { Argon2KdfConfig, KdfConfig, @@ -25,9 +26,10 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im i18nService: I18nService, cipherService: CipherService, pinService: PinServiceAbstraction, + accountService: AccountService, private promptForPassword_callback: () => Promise, ) { - super(cryptoService, i18nService, cipherService, pinService); + super(cryptoService, i18nService, cipherService, pinService, accountService); } async parse(data: string): Promise { diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index db360794474..e44c8f6aa98 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -27,6 +28,7 @@ describe("ImportService", () => { let collectionService: MockProxy; let cryptoService: MockProxy; let pinService: MockProxy; + let accountService: MockProxy; beforeEach(() => { cipherService = mock(); @@ -45,6 +47,7 @@ describe("ImportService", () => { collectionService, cryptoService, pinService, + accountService, ); }); diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index 462d33b8d1d..13b77fb5b4e 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -1,4 +1,7 @@ +import { firstValueFrom, map } from "rxjs"; + import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ImportCiphersRequest } from "@bitwarden/common/models/request/import-ciphers.request"; import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/request/import-organization-ciphers.request"; import { KvpRequest } from "@bitwarden/common/models/request/kvp.request"; @@ -102,6 +105,7 @@ export class ImportService implements ImportServiceAbstraction { private collectionService: CollectionService, private cryptoService: CryptoService, private pinService: PinServiceAbstraction, + private accountService: AccountService, ) {} getImportOptions(): ImportOption[] { @@ -206,6 +210,7 @@ export class ImportService implements ImportServiceAbstraction { this.i18nService, this.cipherService, this.pinService, + this.accountService, promptForPassword_callback, ); case "lastpasscsv": @@ -332,8 +337,11 @@ export class ImportService implements ImportServiceAbstraction { private async handleIndividualImport(importResult: ImportResult) { const request = new ImportCiphersRequest(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); for (let i = 0; i < importResult.ciphers.length; i++) { - const c = await this.cipherService.encrypt(importResult.ciphers[i]); + const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId); request.ciphers.push(new CipherRequest(c)); } if (importResult.folders != null) { @@ -352,9 +360,12 @@ export class ImportService implements ImportServiceAbstraction { private async handleOrganizationalImport(importResult: ImportResult, organizationId: string) { const request = new ImportOrganizationCiphersRequest(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); for (let i = 0; i < importResult.ciphers.length; i++) { importResult.ciphers[i].organizationId = organizationId; - const c = await this.cipherService.encrypt(importResult.ciphers[i]); + const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId); request.ciphers.push(new CipherRequest(c)); } if (importResult.collections != null) { diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index 390b9e0b5a1..0c3e94178f6 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -1,8 +1,9 @@ import * as papa from "papaparse"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -42,6 +43,7 @@ export class OrganizationVaultExportService cryptoFunctionService: CryptoFunctionService, private collectionService: CollectionService, kdfConfigService: KdfConfigService, + private accountService: AccountService, ) { super(pinService, cryptoService, cryptoFunctionService, kdfConfigService); } @@ -87,6 +89,9 @@ export class OrganizationVaultExportService const decCollections: CollectionView[] = []; const decCiphers: CipherView[] = []; const promises = []; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); promises.push( this.apiService.getOrganizationExport(organizationId).then((exportData) => { @@ -111,7 +116,7 @@ export class OrganizationVaultExportService const cipher = new Cipher(new CipherData(c)); exportPromises.push( this.cipherService - .getKeyForCipherKeyDecryption(cipher) + .getKeyForCipherKeyDecryption(cipher, activeUserId) .then((key) => cipher.decrypt(key)) .then((decCipher) => { decCiphers.push(decCipher); diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html index 07606add8b4..626934b20e8 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html @@ -74,6 +74,21 @@ bitPasswordInputToggle [(toggled)]="showFilePassword" > + + {{ "exportPasswordDescription" | i18n }} diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 8f2c6661fd9..d83d189cd79 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -24,6 +24,7 @@ import { EventType } from "@bitwarden/common/enums"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { @@ -38,6 +39,7 @@ import { SelectModule, ToastService, } from "@bitwarden/components"; +import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; import { EncryptedExportType } from "../enums/encrypted-export-type.enum"; @@ -157,6 +159,8 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { protected toastService: ToastService, protected exportService: VaultExportServiceAbstraction, protected eventCollectionService: EventCollectionService, + protected passwordGenerationService: PasswordGenerationServiceAbstraction, + private platformUtilsService: PlatformUtilsService, private policyService: PolicyService, private logService: LogService, private formBuilder: UntypedFormBuilder, @@ -272,6 +276,22 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { } } + generatePassword = async () => { + const [options] = await this.passwordGenerationService.getOptions(); + this.filePasswordValue = await this.passwordGenerationService.generatePassword(options); + this.exportForm.get("filePassword").setValue(this.filePasswordValue); + this.exportForm.get("confirmFilePassword").setValue(this.filePasswordValue); + }; + + copyPasswordToClipboard = async () => { + this.platformUtilsService.copyToClipboard(this.filePasswordValue); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("password")), + }); + }; + submit = async () => { if (this.isFileEncryptedExport && this.filePassword != this.confirmFilePassword) { this.toastService.showToast({ diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 5a2d571c802..1ee9a985f5a 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -3,11 +3,13 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; @@ -15,6 +17,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ButtonComponent, ToastService } from "@bitwarden/components"; import { DownloadAttachmentComponent } from "@bitwarden/vault"; +import { FakeAccountService, mockAccountServiceWith } from "../../../../../common/spec"; + import { CipherAttachmentsComponent } from "./cipher-attachments.component"; import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component"; @@ -49,6 +53,9 @@ describe("CipherAttachmentsComponent", () => { const cipherServiceGet = jest.fn().mockResolvedValue(cipherDomain); const saveAttachmentWithServer = jest.fn().mockResolvedValue(cipherDomain); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { cipherServiceGet.mockClear(); showToast.mockClear(); @@ -75,6 +82,10 @@ describe("CipherAttachmentsComponent", () => { { provide: LogService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, { provide: PlatformUtilsService, useValue: mock() }, + { + provide: AccountService, + useValue: accountService, + }, ], }) .overrideComponent(CipherAttachmentsComponent, { @@ -219,7 +230,7 @@ describe("CipherAttachmentsComponent", () => { it("calls `saveAttachmentWithServer`", async () => { await component.submit(); - expect(saveAttachmentWithServer).toHaveBeenCalledWith(cipherDomain, file); + expect(saveAttachmentWithServer).toHaveBeenCalledWith(cipherDomain, file, mockUserId); }); it("resets form and input values", async () => { diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index 000963d1849..a6febe48978 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -19,11 +19,13 @@ import { ReactiveFormsModule, Validators, } from "@angular/forms"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; @@ -90,6 +92,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { }); private cipherDomain: Cipher; + private activeUserId: UserId; private destroy$ = inject(DestroyRef); constructor( @@ -98,6 +101,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { private formBuilder: FormBuilder, private logService: LogService, private toastService: ToastService, + private accountService: AccountService, ) { this.attachmentForm.statusChanges.pipe(takeUntilDestroyed()).subscribe((status) => { if (!this.submitBtn) { @@ -110,8 +114,11 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { async ngOnInit(): Promise { this.cipherDomain = await this.cipherService.get(this.cipherId); + this.activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), ); // Update the initial state of the submit button @@ -178,11 +185,12 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { this.cipherDomain = await this.cipherService.saveAttachmentWithServer( this.cipherDomain, file, + this.activeUserId, ); // re-decrypt the cipher to update the attachments this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain), + await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), ); // Reset reactive form and input element diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index e8bb1099d55..8e73d9edd40 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -1,5 +1,7 @@ import { inject, Injectable } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -14,15 +16,25 @@ function isSetEqual(a: Set, b: Set) { @Injectable() export class DefaultCipherFormService implements CipherFormService { private cipherService: CipherService = inject(CipherService); + private accountService: AccountService = inject(AccountService); async decryptCipher(cipher: Cipher): Promise { - return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher)); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + return await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); } async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise { // Passing the original cipher is important here as it is responsible for appending to password history + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const encryptedCipher = await this.cipherService.encrypt( cipher, + activeUserId, null, null, config.originalCipher ?? null, @@ -34,7 +46,7 @@ export class DefaultCipherFormService implements CipherFormService { if (cipher.id == null) { savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin); return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), ); } @@ -68,7 +80,7 @@ export class DefaultCipherFormService implements CipherFormService { } return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher), + await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), ); } } diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html index 65a80009ce0..c3da6c0a959 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html @@ -8,12 +8,20 @@ {{ "website" | i18n }} - +
diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index 68c80a7bd52..a675384ff9f 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -1,4 +1,8 @@ + + {{ "cardExpiredMessage" | i18n }} + + ; collections$: Observable; private destroyed$: Subject = new Subject(); + cardIsExpired: boolean = false; constructor( private organizationService: OrganizationService, @@ -57,6 +60,8 @@ export class CipherViewComponent implements OnInit, OnDestroy { async ngOnInit() { await this.loadCipherData(); + + this.cardIsExpired = this.isCardExpiryInThePast(); } ngOnDestroy(): void { @@ -97,4 +102,24 @@ export class CipherViewComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroyed$)); } } + + isCardExpiryInThePast() { + if (this.cipher.card) { + const { expMonth, expYear }: CardView = this.cipher.card; + + if (expYear && expMonth) { + // `Date` months are zero-indexed + const parsedMonth = parseInt(expMonth) - 1; + const parsedYear = parseInt(expYear); + + // First day of the next month minus one, to get last day of the card month + const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); + const now = new Date(); + + return cardExpiry < now; + } + } + + return false; + } } diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 6aeb58c9851..10b0a1a07c3 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -2,8 +2,8 @@

{{ "loginCredentials" | i18n }}

- - + + {{ "username" | i18n }} @@ -23,10 +23,10 @@ [valueLabel]="'username' | i18n" showToast [appA11yTitle]="'copyValue' | i18n" - data-testid="toggle-username" + data-testid="copy-username" > - + {{ "password" | i18n }} - +
- - +
+ + {{ "typePasskey" | i18n }} + + + {{ "verificationCodeTotp" | i18n }} (); constructor( @@ -172,6 +175,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private collectionService: CollectionService, private formBuilder: FormBuilder, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -179,6 +183,10 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI FeatureFlag.RestrictProviderAccess, ); + this.activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const onlyPersonalItems = this.params.ciphers.every((c) => c.organizationId == null); if (this.selectedOrgId === MY_VAULT_ID || onlyPersonalItems) { @@ -420,6 +428,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI shareableCiphers, organizationId, selectedCollectionIds, + this.activeUserId, ); this.toastService.showToast({ @@ -460,7 +469,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private async updateAssignedCollections(cipherView: CipherView) { const { collections } = this.formGroup.getRawValue(); cipherView.collectionIds = collections.map((i) => i.id as CollectionId); - const cipher = await this.cipherService.encrypt(cipherView); + const cipher = await this.cipherService.encrypt(cipherView, this.activeUserId); await this.cipherService.saveCollectionsWithServer(cipher); } } diff --git a/package-lock.json b/package-lock.json index 1fdd4d7718a..eed0eda0958 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,17 +85,17 @@ "@electron/notarize": "2.4.0", "@electron/rebuild": "3.6.0", "@ngtools/webpack": "16.2.14", - "@storybook/addon-a11y": "8.2.6", - "@storybook/addon-actions": "8.2.6", + "@storybook/addon-a11y": "8.2.9", + "@storybook/addon-actions": "8.2.9", "@storybook/addon-designs": "8.0.3", - "@storybook/addon-essentials": "8.2.6", - "@storybook/addon-interactions": "8.2.6", - "@storybook/addon-links": "8.2.6", - "@storybook/angular": "8.2.6", - "@storybook/manager-api": "8.2.6", - "@storybook/theming": "8.2.6", + "@storybook/addon-essentials": "8.2.9", + "@storybook/addon-interactions": "8.2.9", + "@storybook/addon-links": "8.2.9", + "@storybook/angular": "8.2.9", + "@storybook/manager-api": "8.2.9", + "@storybook/theming": "8.2.9", "@types/argon2-browser": "1.18.4", - "@types/chrome": "0.0.262", + "@types/chrome": "0.0.270", "@types/firefox-webext-browser": "111.0.5", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", @@ -120,11 +120,11 @@ "@typescript-eslint/parser": "7.16.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.12.1", - "autoprefixer": "10.4.19", + "autoprefixer": "10.4.20", "babel-loader": "9.1.3", "base64-loader": "1.0.0", "browserslist": "4.23.2", - "chromatic": "10.9.6", + "chromatic": "11.7.1", "concurrently": "8.2.2", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", @@ -169,7 +169,7 @@ "rimraf": "6.0.1", "sass": "1.74.1", "sass-loader": "14.2.1", - "storybook": "8.2.6", + "storybook": "8.2.9", "style-loader": "3.3.4", "tailwindcss": "3.4.10", "ts-jest": "29.2.2", @@ -232,7 +232,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.8.0", + "version": "2024.8.1", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -7027,12 +7027,13 @@ } }, "node_modules/@storybook/addon-a11y": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-8.2.6.tgz", - "integrity": "sha512-GRD50atWJz/V9E6Wyvx4HZWIBAR9m8A0Zn+yBoDVmWzXPOKI62HmWG5zpznCUR7stTt+gAkda1HG7H4KysTZWg==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-8.2.9.tgz", + "integrity": "sha512-9zm0Ecn2KUUKZbRsQM5l2KcQ8RHK6a9eqdQtOMjGagrdUvUstcf7XjBmV1W6PQE2Urj93ciz1cgx4T1AYQyKtA==", "dev": true, + "license": "MIT", "dependencies": { - "@storybook/addon-highlight": "8.2.6", + "@storybook/addon-highlight": "8.2.9", "axe-core": "^4.2.0" }, "funding": { @@ -7040,14 +7041,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-actions": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.2.6.tgz", - "integrity": "sha512-iCsf3V28/jJ95w2zd8aSvR4denoA2UYV3fpNCTGOURqICyKOG3cyVxvqKp8Hhcwn7trNOsK+HlL6q5gpv56ViA==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.2.9.tgz", + "integrity": "sha512-eh2teOqjga7aoClDVV+/b1gHJqsPwjiU1t+Hg/l4i2CkaBUNdYMEL90nR6fgReOdvvL5YhcPwJ8w38f9TrQcoQ==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "@types/uuid": "^9.0.1", @@ -7060,14 +7062,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-backgrounds": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.2.6.tgz", - "integrity": "sha512-61NFowA6EmCw+Eyzp0U4fat9MlPDdnT7aoDyzqSImLwWLITY9IvmWuTeo7XKJZN3fe22z1r7cZseKdYrtaHcKw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.2.9.tgz", + "integrity": "sha512-eGmZAd742ORBbQ6JepzBCko/in62T4Xg9j9LVa+Cvz/7L1C/RQSuU6sUwbRAsXaz+PMVDksPDCUUNsXl3zUL7w==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3", @@ -7078,14 +7081,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-controls": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.2.6.tgz", - "integrity": "sha512-EHUwHy+oZZv3pXzN7fuXWrS/meHFjqcELY3RBvOyEkGf21agl6co6R1tnf6d5N5QoYAGfIbDO7dkauSL2RfNAw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.2.9.tgz", + "integrity": "sha512-vaSE78KOE7SO0GrW4e+mdQphSNpvCX/FGybIRxyaKX9h8smoyUwRNHVyCS3ROHTwH324QWu7GDzsOVrnyXOv0A==", "dev": true, + "license": "MIT", "dependencies": { "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -7096,7 +7100,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-designs": { @@ -7133,17 +7137,18 @@ } }, "node_modules/@storybook/addon-docs": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.2.6.tgz", - "integrity": "sha512-qe7hxntaezqjKdU9QS+Q9NFL6i/uNdBxdvOnCKgPhBAY/zY6yhk5t3sOvonynPK5nkaNAowfSNPIzNxAXlJ1sA==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.2.9.tgz", + "integrity": "sha512-flDOxFIGmXg+6lVdwTLMOKsGob1WrT7rG98mn1SNW0Nxhg3Wg+9pQuq1GLxEzKtAgSflmu+xcBRfYhsogyDXkw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@mdx-js/react": "^3.0.0", - "@storybook/blocks": "8.2.6", - "@storybook/csf-plugin": "8.2.6", + "@storybook/blocks": "8.2.9", + "@storybook/csf-plugin": "8.2.9", "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "8.2.6", + "@storybook/react-dom-shim": "8.2.9", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "fs-extra": "^11.1.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", @@ -7157,24 +7162,25 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-essentials": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.2.6.tgz", - "integrity": "sha512-diGjGZcZNov+RCAVQBTm8JKP2kUtMRuJIQFBeXdPWpu6hYBk6lw1FlAf2GywWGCvdny1pJT90hfoD33qUMNuDg==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.2.9.tgz", + "integrity": "sha512-B2d3eznGZvPIyCVtYX0UhrYcEfK+3Y2sACmEWpSwtk8KXomFEsZnD95m397BYDRw3/X6qeSLWxqgMfqDTEDeMA==", "dev": true, + "license": "MIT", "dependencies": { - "@storybook/addon-actions": "8.2.6", - "@storybook/addon-backgrounds": "8.2.6", - "@storybook/addon-controls": "8.2.6", - "@storybook/addon-docs": "8.2.6", - "@storybook/addon-highlight": "8.2.6", - "@storybook/addon-measure": "8.2.6", - "@storybook/addon-outline": "8.2.6", - "@storybook/addon-toolbars": "8.2.6", - "@storybook/addon-viewport": "8.2.6", + "@storybook/addon-actions": "8.2.9", + "@storybook/addon-backgrounds": "8.2.9", + "@storybook/addon-controls": "8.2.9", + "@storybook/addon-docs": "8.2.9", + "@storybook/addon-highlight": "8.2.9", + "@storybook/addon-measure": "8.2.9", + "@storybook/addon-outline": "8.2.9", + "@storybook/addon-toolbars": "8.2.9", + "@storybook/addon-viewport": "8.2.9", "ts-dedent": "^2.0.0" }, "funding": { @@ -7182,14 +7188,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-highlight": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.2.6.tgz", - "integrity": "sha512-03cV9USsfP3bS4wYV06DYcIaGPfoheQe53Q0Jr1B2yJUVyIPKvmO2nGjLBsqzeL3Wl7vSfLQn0/dUdxCcbqLsw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.2.9.tgz", + "integrity": "sha512-qdcazeNQoo9QKIq+LJJZZXvFZoLn+i4uhbt1Uf9WtW6oU/c1qxORGVD7jc3zsxbQN9nROVPbJ76sfthogxeqWA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0" }, @@ -7198,18 +7205,19 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-interactions": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.2.6.tgz", - "integrity": "sha512-YXpHf8jWPz9HJV+Fw4GaunaCWeE6uqF24aLXdAd8xuhN1UfWJeNV6AwAvFQ0hTLqvmz0yMhX/5JXDKeKESoYDA==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.2.9.tgz", + "integrity": "sha512-oSxBkqpmp1Vm9v/G8mZeFNXD8k6T1NMgzUWzAx7R5m31rfObhoi5Fo1bKQT5BAhSSsdjjd7owTAFKdhwSotSKg==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/instrumenter": "8.2.6", - "@storybook/test": "8.2.6", + "@storybook/instrumenter": "8.2.9", + "@storybook/test": "8.2.9", "polished": "^4.2.2", "ts-dedent": "^2.2.0" }, @@ -7218,14 +7226,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-links": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.2.6.tgz", - "integrity": "sha512-CUuU3nk8wyZ3bljCmOG/OCKazan+bPuNbCph8N763zyzdEx5M/CbBxV9d3pi3zjYpix7txlqrl2/YdMCejfyFw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.2.9.tgz", + "integrity": "sha512-RhJzUNdDb7lbliwXb64HMwieIeJ+OQ2Ditue1vmSox6NsSd+pshR+okHpAyoP1+fW+dahNENwAS2Kt2QiI78FA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", "@storybook/global": "^5.0.0", @@ -7237,7 +7246,7 @@ }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.6" + "storybook": "^8.2.9" }, "peerDependenciesMeta": { "react": { @@ -7246,10 +7255,11 @@ } }, "node_modules/@storybook/addon-measure": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.2.6.tgz", - "integrity": "sha512-neI8YeSOAtOmzasLxo6O8ZLr2ebMaD7XVF+kYatl5+SpyuwwvUGcP9NkKe5S+mB8V2zxFUIsXS74XrhmQhRoaQ==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.2.9.tgz", + "integrity": "sha512-XUfQtYRKWB2dfbPRmHuos816wt1JrLbtRld5ZC8J8ljeqZ4hFBPTQcgI5GAzZqjQuclLC0KuhlA/0bKxdxMMGA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "tiny-invariant": "^1.3.1" @@ -7259,14 +7269,15 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-outline": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.2.6.tgz", - "integrity": "sha512-uAlPtqDWlq7MQQ4zJT80qdjbSdLF/zsvtPhidX6h9cjLKNPWAv79xJQ14AJHaMv+Hzy5xKnM4wdEhgPbzKabQg==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.2.9.tgz", + "integrity": "sha512-p22kI4W7MT0YJOCmg/FfhfH+NpZEDA5tgwstjazSg4ertyhaxziMwWZWiK2JCg0gOAfRJjoYjHz+6/u56iXwgQ==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "ts-dedent": "^2.0.0" @@ -7276,27 +7287,29 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-toolbars": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.2.6.tgz", - "integrity": "sha512-0JmRirMpxHS6VZzBk0kY871xWTpkk3TN4S1sxoFf5fcnCfVTHDjEJ5Ws/QWru1RJlIZHuJKRdQIA6Vuq5X+KfQ==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.2.9.tgz", + "integrity": "sha512-9LMZZ2jRD86Jh6KXedDbAYs4eHj9HtJA9VhSEE2wiqMGwXozpySi7B1GWniNzmFfcgMQ4JHfmD/OrBVTK7Ca/w==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/addon-viewport": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.2.6.tgz", - "integrity": "sha512-IAxH9H8tVFzSmZhKf5E+EALiAdkp19RzGqP/rWluD8LH7oW5HumQE/4oN0ZhVMy1RxYsCKFYjWyAp7AuxeMRSw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.2.9.tgz", + "integrity": "sha512-lyM24+DJEt8R0YZkJKee34NQWv0REACU6lYDalqJNdKS1sEwzLGWxg1hZXnw2JFdBID9NGVvyYU2w6LDozOB0g==", "dev": true, + "license": "MIT", "dependencies": { "memoizerific": "^1.11.3" }, @@ -7305,22 +7318,23 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/angular": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/angular/-/angular-8.2.6.tgz", - "integrity": "sha512-kzOA4H09oDMq2KAg3iVDo0cWFx4u8qYbrMaMCbd/UjuSYd1Qx965Dx0m0i5i6t7BObUe5R5RlDhBo/JXAyQ55g==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/angular/-/angular-8.2.9.tgz", + "integrity": "sha512-E2rmWJOgwHa2SASsTuJMIV/eNywTz8TloRHld2BfoZCBmox8Wf9PrcnyPQ8BmmJUygfs2Vrh3wmrqzrdB8oTHw==", "dev": true, + "license": "MIT", "dependencies": { - "@storybook/builder-webpack5": "8.2.6", - "@storybook/components": "^8.2.6", - "@storybook/core-webpack": "8.2.6", + "@storybook/builder-webpack5": "8.2.9", + "@storybook/components": "^8.2.9", + "@storybook/core-webpack": "8.2.9", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "^8.2.6", - "@storybook/preview-api": "^8.2.6", - "@storybook/theming": "^8.2.6", + "@storybook/manager-api": "^8.2.9", + "@storybook/preview-api": "^8.2.9", + "@storybook/theming": "^8.2.9", "@types/node": "^18.0.0", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", @@ -7355,7 +7369,7 @@ "@angular/platform-browser": ">=15.0.0 < 19.0.0", "@angular/platform-browser-dynamic": ">=15.0.0 < 19.0.0", "rxjs": "^6.0.0 || ^7.4.0", - "storybook": "^8.2.6", + "storybook": "^8.2.9", "typescript": "^4.0.0 || ^5.0.0", "zone.js": ">= 0.11.1 < 1.0.0" }, @@ -7366,19 +7380,21 @@ } }, "node_modules/@storybook/angular/node_modules/@types/node": { - "version": "18.19.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", - "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", + "version": "18.19.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.44.tgz", + "integrity": "sha512-ZsbGerYg72WMXUIE9fYxtvfzLEuq6q8mKERdWFnqTmOvudMxnz+CBNRoOwJ2kNpFOncrKjT1hZwxjlFgQ9qvQA==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@storybook/blocks": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.2.6.tgz", - "integrity": "sha512-nMlZJjVTyfOJ6xwORptsNuS1AZZlDbJUVXc2R8uukGd5GIXxxCdrPk4NvUsjfQslMT9LhYuFld3z62FATsM2rw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.2.9.tgz", + "integrity": "sha512-5276q/s/UL8arwftuBXovUNHqYo/HPQFMGXEmjVVAMXUyFjzEAfKj3+xU897J6AuL+7XVZG32WnqA+X6LJMrcQ==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", "@storybook/global": "^5.0.0", @@ -7402,7 +7418,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.6" + "storybook": "^8.2.9" }, "peerDependenciesMeta": { "react": { @@ -7414,12 +7430,13 @@ } }, "node_modules/@storybook/builder-webpack5": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.2.6.tgz", - "integrity": "sha512-ba25XOXifbAxUYprw5WWcrYq/2DJODFoOHdv7YZqzjKeDDbg1Us8F+72zlBCdr38wY4V9084Sd8EBVXV5bxzRQ==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.2.9.tgz", + "integrity": "sha512-D3oYk4LkteWZ3QLcdUTu/0rUvVNUp/bWwEKAycZDr2uFCOhv8VoS2/l/TaHjn3wpyWpVVKS6GgdP72K++YVufg==", "dev": true, + "license": "MIT", "dependencies": { - "@storybook/core-webpack": "8.2.6", + "@storybook/core-webpack": "8.2.9", "@types/node": "^18.0.0", "@types/semver": "^7.3.4", "browser-assert": "^1.2.1", @@ -7452,7 +7469,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" }, "peerDependenciesMeta": { "typescript": { @@ -7479,15 +7496,16 @@ } }, "node_modules/@storybook/codemod": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.2.6.tgz", - "integrity": "sha512-+mFJ6R+JhJLpU7VPDlXU5Yn6nqIBq745GaEosnIiFOdNo3jaxJ58wq/sGhbQvoCHPUxMA+sDQvR7pS62YFoLRQ==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-8.2.9.tgz", + "integrity": "sha512-3yRx1lFMm1FXWVv+CKDiYM4gOQPEfpcZAQrjfcumxSDUrB091pnU1PeI92Prj3vCdi4+0oPNuN4yDGNUYTMP/A==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/preset-env": "^7.24.4", "@babel/types": "^7.24.0", - "@storybook/core": "8.2.6", + "@storybook/core": "8.2.9", "@storybook/csf": "0.1.11", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", @@ -7564,23 +7582,25 @@ } }, "node_modules/@storybook/components": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.2.6.tgz", - "integrity": "sha512-H8ckH1AnLkHtMtvJ3J8LxnmDtHxkJ7NJacGctHMRrsBIvdKTVwlT4su5nAVVJlan/PrEou+jESfw+OjjBYE5PA==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.2.9.tgz", + "integrity": "sha512-OkkcZ/f/6o3GdFEEK9ZHKIGHWUHmavZUYs5xaSgU64bOrA2aqEFtfeWWitZYTv3Euhk8MVLWfyEMDfez0AlvDg==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/core": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.2.6.tgz", - "integrity": "sha512-XY71g3AcpD6IiER9k9Lt+vlUMYfPIYgWekd7e0Ggzz2gJkPuLunKEdQccLGDSHf5OFAobHhrTJc7ZsvWhmDMag==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.2.9.tgz", + "integrity": "sha512-wSER8FpA6Il/jPyDfKm3yohxDtuhisNPTonMVzd3ulNWR4zERLddyO3HrHJJwdqYHLNk4SBFzwMGpQZVws1y0w==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", "@types/express": "^4.17.21", @@ -7600,10 +7620,11 @@ } }, "node_modules/@storybook/core-webpack": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.2.6.tgz", - "integrity": "sha512-RSqRVNrxrp2pKoQeSmaiHMz7GvAzQ7BV+qPi9gDRDDCuAPrjpY8a17KyqmCJ617asDAb+OEQNBks802xM3pEQw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.2.9.tgz", + "integrity": "sha512-6yL1su+d8IOTU+UkZqM9SeBcVc/G6vUHLsMdlWNyVtRus2JTMmT0K0/ll56jrm/ym0y98cxUOA1jsImkBubP2Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "^18.0.0", "ts-dedent": "^2.0.0" @@ -7613,23 +7634,25 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/core-webpack/node_modules/@types/node": { - "version": "18.19.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", - "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", + "version": "18.19.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.44.tgz", + "integrity": "sha512-ZsbGerYg72WMXUIE9fYxtvfzLEuq6q8mKERdWFnqTmOvudMxnz+CBNRoOwJ2kNpFOncrKjT1hZwxjlFgQ9qvQA==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@storybook/core/node_modules/@types/node": { - "version": "18.19.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", - "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", + "version": "18.19.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.44.tgz", + "integrity": "sha512-ZsbGerYg72WMXUIE9fYxtvfzLEuq6q8mKERdWFnqTmOvudMxnz+CBNRoOwJ2kNpFOncrKjT1hZwxjlFgQ9qvQA==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -7644,10 +7667,11 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.2.6.tgz", - "integrity": "sha512-USn7E/bMQYVqvFBuW6d9rKoSuCImjk0BAmc/0wIOuMQ/yQNp2Xze0m8eVkNHUIUDokyx0TXDjRjwq10Xxk16ag==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.2.9.tgz", + "integrity": "sha512-QQCFb3g12VQQEraDV1UfCmniGhQZKyT6oEt1Im6dzzPJj9NQk+6BjWoDep33CZhBHWoLryrMQd2fjuHxnFRNEA==", "dev": true, + "license": "MIT", "dependencies": { "unplugin": "^1.3.1" }, @@ -7656,7 +7680,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/global": { @@ -7679,10 +7703,11 @@ } }, "node_modules/@storybook/instrumenter": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.2.6.tgz", - "integrity": "sha512-RxtpcMTUSq8/wPM6cR6EXVrPEiNuRbC71cIFVFZagOFYvnnOKwSPV+GOLPK0wxMbGB4c5/+Xe8ADefmZTvxOsA==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.2.9.tgz", + "integrity": "sha512-+DNjTbsMzlDggsvkhRuOy7aGvQJ4oLCPgunP5Se/3yBjG+M2bYDa0EmC5jC2nwZ3ffpuvbzaVe7fWf7R8W9F2Q==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "@vitest/utils": "^1.3.1", @@ -7693,40 +7718,43 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/manager-api": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.2.6.tgz", - "integrity": "sha512-uv36h/b5RhlajWtEg4cVPBYV8gZs6juux0nIE+6G9i7vt8Ild6gM9tW1KNabgZcaHFiyWJYCNWxJZoKjgUmXDg==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.2.9.tgz", + "integrity": "sha512-mkYvUlfqDw+0WbxIynh5TcrotmoXlumEsOA4+45zuNea8XpEgj5cNBUCnmfEO6yQ85swqkS8YYbMpg1cZyu/Vw==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/preview-api": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.2.6.tgz", - "integrity": "sha512-5vTj2ndX5ng4nDntZYe+r8UwLjCIGFymhq5/r2adAvRKL+Bo4zQDWGO7bhvGJk16do2THb2JvPz49ComW9LLZw==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.2.9.tgz", + "integrity": "sha512-D8/t+a78OJqQAcT/ABa1C4YM/OaLGQ9IvCsp3Q9ruUqDCwuZBj8bG3D4477dlY4owX2ycC0rWYu3VvuK0EmJjA==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/react-dom-shim": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.2.6.tgz", - "integrity": "sha512-B+x8UAEQPDp1yhN3tMh09NvSL38QNfJB7PAyLgKrfE7xIAzvewq+RLW2DfGkoZCy+Zr7QSHm1p7NOgud8+sQCg==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.2.9.tgz", + "integrity": "sha512-uCAjSQEsNk8somVn1j/I1G9G/uUax5byHseIIV0Eq3gVXttGd7gaWcP+TDHtqIaenWHx4l+hCSuCesxiLWmx4Q==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" @@ -7734,17 +7762,18 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/test": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.2.6.tgz", - "integrity": "sha512-nTzNxReBcMRlX1+8PNU/MuA9ArFbeQhfZXMBIwJJoHOhnNe1knYpyn1++xINxAHKOh0BBhQ0NIMoKdcGmW3V6w==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.2.9.tgz", + "integrity": "sha512-O5JZ5S8UVVR7V0ru5AiF/uRO+srAVwji0Iik7ihy8gw3V91WQNMmJh2KkdhG0R1enYeBsYZlipOm+AW7f/MmOA==", "dev": true, + "license": "MIT", "dependencies": { "@storybook/csf": "0.1.11", - "@storybook/instrumenter": "8.2.6", + "@storybook/instrumenter": "8.2.9", "@testing-library/dom": "10.1.0", "@testing-library/jest-dom": "6.4.5", "@testing-library/user-event": "14.5.2", @@ -7757,20 +7786,21 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@storybook/theming": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.2.6.tgz", - "integrity": "sha512-ICnYuLIVsYifVCMQljdHgrp+5vAquNybHxDGWiPeOxBicotwHF8rLhTckD2CdVQbMp0jk6r6jetvjXbFJ2MbvQ==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.2.9.tgz", + "integrity": "sha512-OL0NFvowPX85N5zIYdgeKKaFm7V4Vgtci093vL3cDZT13LGH6GuEzJKkUFGuUGNPFlJc+EgTj0o6PYKrOLyQ6w==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.2.6" + "storybook": "^8.2.9" } }, "node_modules/@szmarczak/http-timer": { @@ -8073,10 +8103,11 @@ } }, "node_modules/@types/chrome": { - "version": "0.0.262", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.262.tgz", - "integrity": "sha512-TOoj3dqSYE13PD2fRuMQ6X6pggEvL9rRk/yOYOyWE6sfqRWxsJm4VoVm+wr9pkr4Sht/M5t7FFL4vXato8d1gA==", + "version": "0.0.270", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.270.tgz", + "integrity": "sha512-ADvkowV7YnJfycZZxL2brluZ6STGW+9oKG37B422UePf2PCXuFA/XdERI0T18wtuWPx0tmFeZqq6MOXVk1IC+Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" @@ -11194,9 +11225,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -11212,12 +11243,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -11230,6 +11262,39 @@ "postcss": "^8.1.0" } }, + "node_modules/autoprefixer/node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -12610,18 +12675,19 @@ } }, "node_modules/chromatic": { - "version": "10.9.6", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-10.9.6.tgz", - "integrity": "sha512-1MoT+/U+vQwEiq2GuehPyStbqhxqHmM1B9pdpVU1dKh26userQg1FyOFYifkTgy+9reo2w2p7sAbc0JRd2kzlA==", + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.7.1.tgz", + "integrity": "sha512-LvgPimdQdnQB07ZDxLEC2KtxgYeqTw0X71GA7fi3zhgtKLxZcE+BSZ/5I9rrQp1V8ydmfElfw0ZwnUH4fVgUAQ==", "dev": true, + "license": "MIT", "bin": { "chroma": "dist/bin.js", "chromatic": "dist/bin.js", "chromatic-cli": "dist/bin.js" }, "peerDependencies": { - "@chromatic-com/cypress": "^0.5.2 || ^1.0.0", - "@chromatic-com/playwright": "^0.5.2 || ^1.0.0" + "@chromatic-com/cypress": "^0.*.* || ^1.0.0", + "@chromatic-com/playwright": "^0.*.* || ^1.0.0" }, "peerDependenciesMeta": { "@chromatic-com/cypress": { @@ -15575,6 +15641,7 @@ "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.4" }, @@ -32142,15 +32209,16 @@ } }, "node_modules/storybook": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.2.6.tgz", - "integrity": "sha512-8j30wDxQmkcqI0fWcSYFsUCjErsY1yTWbTW+yjbwM8DyW18Cud6CwbFRCxjFsH+2M0CjP6Pqs/m1PGI0vcQscQ==", + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.2.9.tgz", + "integrity": "sha512-S7Q/Yt4A+nu1O23rg39lQvBqL2Vg+PKXbserDWUR4LFJtfmoZ2xGO8oFIhJmvvhjUBvolw1q7QDeswPq2i0sGw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/types": "^7.24.0", - "@storybook/codemod": "8.2.6", - "@storybook/core": "8.2.6", + "@storybook/codemod": "8.2.9", + "@storybook/core": "8.2.9", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -32191,6 +32259,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -32200,6 +32269,7 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -32216,6 +32286,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, + "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", @@ -32236,6 +32307,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -32248,6 +32320,7 @@ "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" }, @@ -34604,10 +34677,11 @@ } }, "node_modules/unplugin": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.0.tgz", - "integrity": "sha512-KeczzHl2sATPQUx1gzo+EnUkmN4VmGBYRRVOZSGvGITE9rGHRDGqft6ONceP3vgXcyJ2XjX5axG5jMWUwNCYLw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.2.tgz", + "integrity": "sha512-bEqQxeC7rxtxPZ3M5V4Djcc4lQqKPgGe3mAWZvxcSmX5jhGxll19NliaRzQSQPrk4xJZSGniK3puLWpRuZN7VQ==", "dev": true, + "license": "MIT", "dependencies": { "acorn": "^8.12.1", "chokidar": "^3.6.0", @@ -34623,6 +34697,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", diff --git a/package.json b/package.json index d19f5981437..99fd8a40499 100644 --- a/package.json +++ b/package.json @@ -47,17 +47,17 @@ "@electron/notarize": "2.4.0", "@electron/rebuild": "3.6.0", "@ngtools/webpack": "16.2.14", - "@storybook/addon-a11y": "8.2.6", - "@storybook/addon-actions": "8.2.6", + "@storybook/addon-a11y": "8.2.9", + "@storybook/addon-actions": "8.2.9", "@storybook/addon-designs": "8.0.3", - "@storybook/addon-essentials": "8.2.6", - "@storybook/addon-interactions": "8.2.6", - "@storybook/addon-links": "8.2.6", - "@storybook/angular": "8.2.6", - "@storybook/manager-api": "8.2.6", - "@storybook/theming": "8.2.6", + "@storybook/addon-essentials": "8.2.9", + "@storybook/addon-interactions": "8.2.9", + "@storybook/addon-links": "8.2.9", + "@storybook/angular": "8.2.9", + "@storybook/manager-api": "8.2.9", + "@storybook/theming": "8.2.9", "@types/argon2-browser": "1.18.4", - "@types/chrome": "0.0.262", + "@types/chrome": "0.0.270", "@types/firefox-webext-browser": "111.0.5", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", @@ -82,11 +82,11 @@ "@typescript-eslint/parser": "7.16.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.12.1", - "autoprefixer": "10.4.19", + "autoprefixer": "10.4.20", "babel-loader": "9.1.3", "base64-loader": "1.0.0", "browserslist": "4.23.2", - "chromatic": "10.9.6", + "chromatic": "11.7.1", "concurrently": "8.2.2", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", @@ -131,7 +131,7 @@ "rimraf": "6.0.1", "sass": "1.74.1", "sass-loader": "14.2.1", - "storybook": "8.2.6", + "storybook": "8.2.9", "style-loader": "3.3.4", "tailwindcss": "3.4.10", "ts-jest": "29.2.2",