diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 436e2d4ad0f..7b6d24aa8c0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -86,9 +86,13 @@ apps/web/src/app/shared @bitwarden/team-platform-dev apps/web/src/translation-constants.ts @bitwarden/team-platform-dev # Workflows .github/workflows/brew-bump-desktop.yml @bitwarden/team-platform-dev +.github/workflows/build-browser-target.yml @bitwarden/team-platform-dev .github/workflows/build-browser.yml @bitwarden/team-platform-dev +.github/workflows/build-cli-target.yml @bitwarden/team-platform-dev .github/workflows/build-cli.yml @bitwarden/team-platform-dev +.github/workflows/build-desktop-target.yml @bitwarden/team-platform-dev .github/workflows/build-desktop.yml @bitwarden/team-platform-dev +.github/workflows/build-web-target.yml @bitwarden/team-platform-dev .github/workflows/build-web.yml @bitwarden/team-platform-dev .github/workflows/chromatic.yml @bitwarden/team-platform-dev .github/workflows/lint.yml @bitwarden/team-platform-dev diff --git a/.github/workflows/build-browser-target.yml b/.github/workflows/build-browser-target.yml new file mode 100644 index 00000000000..3334326920c --- /dev/null +++ b/.github/workflows/build-browser-target.yml @@ -0,0 +1,33 @@ +name: Build Browser on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/browser/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + workflow_call: + inputs: {} + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build Browser on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-browser.yml + secrets: inherit + diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index c9e9f588c83..18f104bdc20 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -1,7 +1,7 @@ name: Build Browser on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -38,19 +38,14 @@ defaults: shell: bash jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: repo_url: ${{ steps.gen_vars.outputs.repo_url }} adj_build_number: ${{ steps.gen_vars.outputs.adj_build_number }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -74,6 +69,14 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + locales-test: name: Locales Test @@ -197,14 +200,10 @@ jobs: npm_command: "dist:edge" archive_name: "dist-edge.zip" artifact_name: "dist-edge-MV3" - - name: "firefox" - npm_command: "dist:firefox" - archive_name: "dist-firefox.zip" - artifact_name: "dist-firefox" - name: "firefox-mv3" npm_command: "dist:firefox:mv3" archive_name: "dist-firefox.zip" - artifact_name: "DO-NOT-USE-FOR-PROD-dist-firefox-MV3" + artifact_name: "dist-firefox-MV3" - name: "opera" npm_command: "dist:opera" archive_name: "dist-opera.zip" @@ -281,6 +280,7 @@ jobs: needs: - setup - locales-test + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: _BUILD_NUMBER: ${{ needs.setup.outputs.adj_build_number }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} diff --git a/.github/workflows/build-cli-target.yml b/.github/workflows/build-cli-target.yml new file mode 100644 index 00000000000..81ec4178681 --- /dev/null +++ b/.github/workflows/build-cli-target.yml @@ -0,0 +1,33 @@ +name: Build CLI on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/cli/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + - '.github/workflows/build-cli.yml' + - 'bitwarden_license/bit-cli/**' + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build CLI on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-cli.yml + secrets: inherit + diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index d5d78811cda..b3694ac423b 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -1,7 +1,7 @@ name: Build CLI on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -27,6 +27,8 @@ on: - '!*.txt' - '.github/workflows/build-cli.yml' - 'bitwarden_license/bit-cli/**' + workflow_call: + inputs: {} workflow_dispatch: inputs: sdk_branch: @@ -39,18 +41,13 @@ defaults: working-directory: apps/cli jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: package_version: ${{ steps.retrieve-package-version.outputs.package_version }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -71,6 +68,14 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + cli: name: CLI ${{ matrix.os.base }} - ${{ matrix.license_type.readable }} strategy: @@ -117,7 +122,7 @@ jobs: working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -130,7 +135,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -272,7 +277,7 @@ jobs: working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -285,7 +290,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ diff --git a/.github/workflows/build-desktop-target.yml b/.github/workflows/build-desktop-target.yml new file mode 100644 index 00000000000..8c26f991174 --- /dev/null +++ b/.github/workflows/build-desktop-target.yml @@ -0,0 +1,32 @@ +name: Build Desktop on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/desktop/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + - '.github/workflows/build-desktop.yml' + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build Desktop on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-desktop.yml + secrets: inherit + diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index ca681dac6b0..23722e7c7df 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1,7 +1,7 @@ name: Build Desktop on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -25,6 +25,8 @@ on: - '!*.md' - '!*.txt' - '.github/workflows/build-desktop.yml' + workflow_call: + inputs: {} workflow_dispatch: inputs: sdk_branch: @@ -37,15 +39,9 @@ defaults: shell: bash jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - electron-verify: name: Verify Electron Version runs-on: ubuntu-22.04 - needs: - - check-run steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -67,8 +63,6 @@ jobs: setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: package_version: ${{ steps.retrieve-version.outputs.package_version }} release_channel: ${{ steps.release-channel.outputs.channel }} @@ -76,6 +70,7 @@ jobs: rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }} hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} defaults: run: working-directory: apps/desktop @@ -138,6 +133,14 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + linux: name: Linux Build # Note, before updating the ubuntu version of the workflow, ensure the snap base image @@ -333,12 +336,14 @@ jobs: rustup show - name: Login to Azure + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Retrieve secrets id: retrieve-secrets + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" @@ -353,7 +358,7 @@ jobs: working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -366,7 +371,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -386,7 +391,17 @@ jobs: working-directory: apps/desktop/desktop_native run: node build.js cross-platform - - name: Build & Sign (dev) + - name: Build + run: | + npm run build + + - name: Pack + if: ${{ needs.setup.outputs.has_secrets == 'false' }} + run: | + npm run pack:win + + - name: Pack & Sign (dev) + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: ELECTRON_BUILDER_SIGN: 1 SIGNING_VAULT_URL: ${{ steps.retrieve-secrets.outputs.code-signing-vault-url }} @@ -395,10 +410,10 @@ jobs: SIGNING_CLIENT_SECRET: ${{ steps.retrieve-secrets.outputs.code-signing-client-secret }} SIGNING_CERT_NAME: ${{ steps.retrieve-secrets.outputs.code-signing-cert-name }} run: | - npm run build npm run pack:win - name: Rename appx files for store + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | Copy-Item "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx" ` -Destination "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx" @@ -408,6 +423,7 @@ jobs: -Destination "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx" - name: Package for Chocolatey + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | Copy-Item -Path ./stores/chocolatey -Destination ./dist/chocolatey -Recurse Copy-Item -Path ./dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe ` @@ -419,6 +435,7 @@ jobs: choco pack ./dist/chocolatey/bitwarden.nuspec --version "$env:_PACKAGE_VERSION" --out ./dist/chocolatey - name: Fix NSIS artifact names for auto-updater + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | Rename-Item -Path .\dist\nsis-web\Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z ` -NewName bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z @@ -435,6 +452,7 @@ jobs: if-no-files-found: error - name: Upload installer exe artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe @@ -442,6 +460,7 @@ jobs: if-no-files-found: error - name: Upload appx ia32 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx @@ -449,6 +468,7 @@ jobs: if-no-files-found: error - name: Upload store appx ia32 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx @@ -456,6 +476,7 @@ jobs: if-no-files-found: error - name: Upload NSIS ia32 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z @@ -463,6 +484,7 @@ jobs: if-no-files-found: error - name: Upload appx x64 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx @@ -470,6 +492,7 @@ jobs: if-no-files-found: error - name: Upload store appx x64 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx @@ -477,6 +500,7 @@ jobs: if-no-files-found: error - name: Upload NSIS x64 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z @@ -484,6 +508,7 @@ jobs: if-no-files-found: error - name: Upload appx ARM64 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx @@ -491,6 +516,7 @@ jobs: if-no-files-found: error - name: Upload store appx ARM64 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx @@ -498,6 +524,7 @@ jobs: if-no-files-found: error - name: Upload NSIS ARM64 artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z @@ -505,6 +532,7 @@ jobs: if-no-files-found: error - name: Upload nupkg artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg @@ -512,6 +540,7 @@ jobs: if-no-files-found: error - name: Upload auto-update artifact + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml @@ -574,11 +603,13 @@ jobs: key: ${{ runner.os }}-${{ github.run_id }}-safari-extension - name: Login to Azure + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Download Provisioning Profiles secrets + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: ACCOUNT_NAME: bitwardenci CONTAINER_NAME: profiles @@ -591,6 +622,7 @@ jobs: --output none - name: Get certificates + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | mkdir -p $HOME/certificates @@ -613,6 +645,7 @@ jobs: jq -r .value | base64 -d > $HOME/certificates/macdev-cert.p12 - name: Set up keychain + if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | @@ -642,6 +675,7 @@ jobs: security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - name: Set up provisioning profiles + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile @@ -661,7 +695,7 @@ jobs: working-directory: ./ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -674,7 +708,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -701,6 +735,7 @@ jobs: browser-build: name: Browser Build needs: setup + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: ./.github/workflows/build-browser.yml secrets: inherit @@ -708,6 +743,7 @@ jobs: macos-package-github: name: MacOS Package GitHub Release Assets runs-on: macos-13 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} needs: - browser-build - macos-build @@ -949,6 +985,7 @@ jobs: macos-package-mas: name: MacOS Package Prod Release Asset runs-on: macos-13 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} needs: - browser-build - macos-build @@ -1217,6 +1254,7 @@ jobs: macos-package-dev: name: MacOS Package Dev Release Asset runs-on: macos-13 + if: ${{ needs.setup.outputs.has_secrets == 'true' }} needs: - browser-build - macos-build diff --git a/.github/workflows/build-web-target.yml b/.github/workflows/build-web-target.yml new file mode 100644 index 00000000000..fb7074292b5 --- /dev/null +++ b/.github/workflows/build-web-target.yml @@ -0,0 +1,32 @@ +name: Build Web on PR Target + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: + - 'l10n_master' + - 'cf-pages' + paths: + - 'apps/web/**' + - 'libs/**' + - '*' + - '!*.md' + - '!*.txt' + - '.github/workflows/build-web.yml' + +defaults: + run: + shell: bash + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + run-workflow: + name: Run Build Web on PR Target + needs: check-run + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: ./.github/workflows/build-web.yml + secrets: inherit + diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index b7e8a51897c..423b15372ae 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -1,7 +1,7 @@ name: Build Web on: - pull_request_target: + pull_request: types: [opened, synchronize] branches-ignore: - 'l10n_master' @@ -27,6 +27,8 @@ on: - '.github/workflows/build-web.yml' release: types: [published] + workflow_call: + inputs: {} workflow_dispatch: inputs: custom_tag_extension: @@ -41,18 +43,13 @@ env: _AZ_REGISTRY: bitwardenprod.azurecr.io jobs: - check-run: - name: Check PR run - uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main - setup: name: Setup runs-on: ubuntu-22.04 - needs: - - check-run outputs: version: ${{ steps.version.outputs.value }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} + has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -70,6 +67,14 @@ jobs: NODE_VERSION=${NODE_NVMRC/v/''} echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + - name: Check secrets + id: check-secrets + env: + AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + run: | + has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }} + echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + build-artifacts: name: Build artifacts runs-on: ubuntu-22.04 @@ -128,7 +133,7 @@ jobs: run: npm ci - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -141,7 +146,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} + if: ${{ inputs.sdk_branch != '' && needs.setup.outputs.has_secrets == 'true' }} working-directory: ./ run: | ls -l ../ @@ -213,19 +218,23 @@ jobs: ########## ACRs ########## - name: Login to Prod Azure + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - name: Log into Prod container registry + if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: az acr login -n bitwardenprod - name: Login to Azure - CI Subscription + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Retrieve github PAT secrets + if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: retrieve-secret-pat uses: bitwarden/gh-actions/get-keyvault-secrets@main with: @@ -273,6 +282,7 @@ jobs: run: echo "name=$_AZ_REGISTRY/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT - name: Build Docker image + if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: build-docker uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: @@ -283,7 +293,7 @@ jobs: tags: ${{ steps.image-name.outputs.name }} secrets: | "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" - + - name: Install Cosign if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5bc566202c6..4fbef027c7c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,12 +1,20 @@ name: Lint on: - push: + pull_request: + types: [opened, synchronize] branches-ignore: - 'l10n_master' - 'cf-pages' paths-ignore: - '.github/workflows/**' + push: + branches: + - 'main' + - 'rc' + - 'hotfix-rc-*' + paths-ignore: + - '.github/workflows/**' workflow_dispatch: inputs: {} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index ac7f0ae6f71..c5e189c4666 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -77,3 +77,4 @@ jobs: -Dsonar.sources=. -Dsonar.test.inclusions=**/*.spec.ts -Dsonar.exclusions=**/*.spec.ts + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} diff --git a/apps/browser/package.json b/apps/browser/package.json index 154543d85cc..c37c476bf94 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.2.0", + "version": "2025.2.1", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", diff --git a/apps/browser/src/auth/popup/register.component.html b/apps/browser/src/auth/popup/register.component.html deleted file mode 100644 index e2f4f2e7d12..00000000000 --- a/apps/browser/src/auth/popup/register.component.html +++ /dev/null @@ -1,147 +0,0 @@ -
-
-
- -
-

- {{ "createAccount" | i18n }} -

-
- -
-
-
-
-
-
- - -
-
-
-
- - -
-
- -
-
- - -
-
- -
-
-
-
-
- - -
-
- -
-
-
- - -
-
- -
-
- - -
-
-
-
- -
-
-
-
- - -
-
-
-
-
diff --git a/apps/browser/src/auth/popup/register.component.ts b/apps/browser/src/auth/popup/register.component.ts deleted file mode 100644 index 50475b2204d..00000000000 --- a/apps/browser/src/auth/popup/register.component.ts +++ /dev/null @@ -1,58 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; -import { Router } from "@angular/router"; - -import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/auth/components/register.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Component({ - selector: "app-register", - templateUrl: "register.component.html", -}) -export class RegisterComponent extends BaseRegisterComponent { - color: string; - text: string; - - constructor( - formValidationErrorService: FormValidationErrorsService, - formBuilder: UntypedFormBuilder, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - keyService: KeyService, - apiService: ApiService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - logService: LogService, - auditService: AuditService, - dialogService: DialogService, - toastService: ToastService, - ) { - super( - formValidationErrorService, - formBuilder, - loginStrategyService, - router, - i18nService, - keyService, - apiService, - platformUtilsService, - environmentService, - logService, - auditService, - dialogService, - toastService, - ); - } -} diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 37c05a55a3a..40c4d07cadf 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -825,6 +825,7 @@ describe("NotificationBackground", () => { queueMessage.newPassword, message.edit, sender.tab, + "testId", ); expect(updateWithServerSpy).toHaveBeenCalled(); expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { @@ -862,6 +863,7 @@ describe("NotificationBackground", () => { queueMessage.password, message.edit, sender.tab, + "testId", ); expect(editItemSpy).not.toHaveBeenCalled(); expect(createWithServerSpy).not.toHaveBeenCalled(); @@ -895,6 +897,7 @@ describe("NotificationBackground", () => { queueMessage.newPassword, message.edit, sender.tab, + "testId", ); expect(editItemSpy).toHaveBeenCalled(); expect(updateWithServerSpy).not.toHaveBeenCalled(); @@ -904,10 +907,13 @@ describe("NotificationBackground", () => { expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "editedCipher", }); - expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + "testId", + ); expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalledWith(sender.tab, { cipherId: cipherView.id, }); @@ -945,7 +951,7 @@ describe("NotificationBackground", () => { queueMessage, message.folder, ); - expect(editItemSpy).toHaveBeenCalledWith(cipherView, sender.tab); + expect(editItemSpy).toHaveBeenCalledWith(cipherView, "testId", sender.tab); expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "closeNotificationBar", }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index a091256b28d..1a99425b7de 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { 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 { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { ExtensionCommand, ExtensionCommandType, @@ -22,6 +23,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -87,8 +89,6 @@ export default class NotificationBackground { bgGetDecryptedCiphers: () => this.getNotificationCipherData(), }; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private autofillService: AutofillService, private cipherService: CipherService, @@ -151,7 +151,13 @@ export default class NotificationBackground { firstValueFrom(this.environmentService.environment$), ]); const iconsServerUrl = env.getIconsUrl(); - const decryptedCiphers = await this.cipherService.getAllDecryptedForUrl(currentTab.url); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + const decryptedCiphers = await this.cipherService.getAllDecryptedForUrl( + currentTab.url, + activeUserId, + ); return decryptedCiphers.map((view) => { const { id, name, reprompt, favorite, login } = view; @@ -304,7 +310,14 @@ export default class NotificationBackground { return; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return; + } + + const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url, activeUserId); const usernameMatches = ciphers.filter( (c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername, ); @@ -382,7 +395,14 @@ export default class NotificationBackground { } let id: string = null; - const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return; + } + + const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId); if (changeData.currentPassword != null) { const passwordMatches = ciphers.filter( (c) => c.login.password === changeData.currentPassword, @@ -535,37 +555,42 @@ export default class NotificationBackground { this.notificationQueue.splice(i, 1); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (queueMessage.type === NotificationQueueMessageType.ChangePassword) { - const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId); - await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab); + const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId); + await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab, activeUserId); return; } // If the vault was locked, check if a cipher needs updating instead of creating a new one if (queueMessage.wasVaultLocked) { - const allCiphers = await this.cipherService.getAllDecryptedForUrl(queueMessage.uri); + const allCiphers = await this.cipherService.getAllDecryptedForUrl( + queueMessage.uri, + activeUserId, + ); const existingCipher = allCiphers.find( (c) => c.login.username != null && c.login.username.toLowerCase() === queueMessage.username, ); if (existingCipher != null) { - await this.updatePassword(existingCipher, queueMessage.password, edit, tab); + await this.updatePassword(existingCipher, queueMessage.password, edit, tab, activeUserId); return; } } - folderId = (await this.folderExists(folderId)) ? folderId : null; + folderId = (await this.folderExists(folderId, activeUserId)) ? folderId : null; const newCipher = this.convertAddLoginQueueMessageToCipherView(queueMessage, folderId); if (edit) { - await this.editItem(newCipher, tab); + await this.editItem(newCipher, activeUserId, tab); await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" }); return; } - const activeUserId = await firstValueFrom(this.activeUserId$); - const cipher = await this.cipherService.encrypt(newCipher, activeUserId); try { await this.cipherService.createWithServer(cipher); @@ -588,24 +613,25 @@ export default class NotificationBackground { * @param newPassword - The new password to update the cipher with * @param edit - Identifies if the cipher should be edited or simply updated * @param tab - The tab that the message was sent from + * @param userId - The active account user ID */ private async updatePassword( cipherView: CipherView, newPassword: string, edit: boolean, tab: chrome.tabs.Tab, + userId: UserId, ) { cipherView.login.password = newPassword; if (edit) { - await this.editItem(cipherView, tab); + await this.editItem(cipherView, userId, tab); await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" }); await BrowserApi.tabSendMessage(tab, { command: "editedCipher" }); return; } - const activeUserId = await firstValueFrom(this.activeUserId$); - const cipher = await this.cipherService.encrypt(cipherView, activeUserId); + const cipher = await this.cipherService.encrypt(cipherView, userId); try { // We've only updated the password, no need to broadcast editedCipher message await this.cipherService.updateWithServer(cipher); @@ -622,33 +648,34 @@ export default class NotificationBackground { * and opens the add/edit vault item popout. * * @param cipherView - The cipher to edit + * @param userId - The active account user ID * @param senderTab - The tab that the message was sent from */ - private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) { - await this.cipherService.setAddEditCipherInfo({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + private async editItem(cipherView: CipherView, userId: UserId, senderTab: chrome.tabs.Tab) { + await this.cipherService.setAddEditCipherInfo( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + userId, + ); await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id }); } - private async folderExists(folderId: string) { + private async folderExists(folderId: string, userId: UserId) { if (Utils.isNullOrWhitespace(folderId) || folderId === "null") { return false; } - const activeUserId = await firstValueFrom(this.activeUserId$); - const folders = await firstValueFrom(this.folderService.folderViews$(activeUserId)); + const folders = await firstValueFrom(this.folderService.folderViews$(userId)); return folders.some((x) => x.id === folderId); } - private async getDecryptedCipherById(cipherId: string) { - const cipher = await this.cipherService.get(cipherId); + private async getDecryptedCipherById(cipherId: string, userId: UserId) { + const cipher = await this.cipherService.get(cipherId, userId); if (cipher != null && cipher.type === CipherType.Login) { - const activeUserId = await firstValueFrom(this.activeUserId$); - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), ); } return null; @@ -685,7 +712,9 @@ export default class NotificationBackground { * Returns the first value found from the folder service's folderViews$ observable. */ private async getFolderData() { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); return await firstValueFrom(this.folderService.folderViews$(activeUserId)); } diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index c3a6357ed05..22531788d37 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -206,6 +206,7 @@ describe("OverlayBackground", () => { inlineMenuFieldQualificationService, themeStateService, totpService, + accountService, generatedPasswordCallbackMock, addPasswordCallbackMock, ); @@ -849,7 +850,7 @@ describe("OverlayBackground", () => { await flushPromises(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [ + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [ CipherType.Card, CipherType.Identity, ]); @@ -872,7 +873,7 @@ describe("OverlayBackground", () => { await flushPromises(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId); expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( new Map([ @@ -891,7 +892,7 @@ describe("OverlayBackground", () => { await flushPromises(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [ + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [ CipherType.Card, CipherType.Identity, ]); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 3d2b1ec783c..1d55a154ee3 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -13,8 +13,10 @@ import { } from "rxjs"; import { parse } from "tldts"; +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 { getOptionalUserId, getUserId } from "@bitwarden/common/auth/services/account.service"; import { AutofillOverlayVisibility, SHOW_AUTOFILL_BUTTON, @@ -34,6 +36,7 @@ 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 { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; @@ -225,6 +228,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService, private themeStateService: ThemeStateService, private totpService: TotpService, + private accountService: AccountService, private generatePasswordCallback: () => Promise, private addPasswordCallback: (password: string) => Promise, ) { @@ -405,13 +409,20 @@ export class OverlayBackground implements OverlayBackgroundInterface { currentTab: chrome.tabs.Tab, updateAllCipherTypes: boolean, ): Promise { - if (updateAllCipherTypes || !this.cardAndIdentityCiphers) { - return this.getAllCipherTypeViews(currentTab); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (!activeUserId) { + return []; } - const cipherViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url || "")).sort( - (a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b), - ); + if (updateAllCipherTypes || !this.cardAndIdentityCiphers) { + return this.getAllCipherTypeViews(currentTab, activeUserId); + } + + const cipherViews = ( + await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", activeUserId) + ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); return this.cardAndIdentityCiphers ? cipherViews.concat(...this.cardAndIdentityCiphers) @@ -422,15 +433,19 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Queries all cipher types from the user's vault returns them sorted by last used. * * @param currentTab - The current tab + * @param userId - The active user id */ - private async getAllCipherTypeViews(currentTab: chrome.tabs.Tab): Promise { + private async getAllCipherTypeViews( + currentTab: chrome.tabs.Tab, + userId: UserId, + ): Promise { if (!this.cardAndIdentityCiphers) { this.cardAndIdentityCiphers = new Set([]); } this.cardAndIdentityCiphers.clear(); const cipherViews = ( - await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", [ + await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", userId, [ CipherType.Card, CipherType.Identity, ]) @@ -2399,10 +2414,14 @@ export class OverlayBackground implements OverlayBackgroundInterface { try { this.closeInlineMenu(sender); - await this.cipherService.setAddEditCipherInfo({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.setAddEditCipherInfo( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + activeUserId, + ); await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id, diff --git a/apps/browser/src/autofill/background/web-request.background.ts b/apps/browser/src/autofill/background/web-request.background.ts index 2c14358a359..22e10a3dd0a 100644 --- a/apps/browser/src/autofill/background/web-request.background.ts +++ b/apps/browser/src/autofill/background/web-request.background.ts @@ -1,7 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -14,6 +18,7 @@ export default class WebRequestBackground { platformUtilsService: PlatformUtilsService, private cipherService: CipherService, private authService: AuthService, + private accountService: AccountService, private readonly webRequest: typeof chrome.webRequest, ) { this.isFirefox = platformUtilsService.isFirefox(); @@ -55,7 +60,16 @@ export default class WebRequestBackground { // eslint-disable-next-line private async resolveAuthCredentials(domain: string, success: Function, error: Function) { - if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + error(); + return; + } + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(activeUserId)); + if (authStatus < AuthenticationStatus.Unlocked) { error(); return; } @@ -63,6 +77,7 @@ export default class WebRequestBackground { try { const ciphers = await this.cipherService.getAllDecryptedForUrl( domain, + activeUserId, null, UriMatchStrategy.Host, ); diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts index 4fed9eee5ef..3228aed4688 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts @@ -2,6 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { 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 { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; @@ -14,6 +16,9 @@ describe("CipherContextMenuHandler", () => { let authService: MockProxy; let cipherService: MockProxy; + const mockUserId = "UserId" as UserId; + const accountService = mockAccountServiceWith(mockUserId); + let sut: CipherContextMenuHandler; beforeEach(() => { @@ -24,7 +29,12 @@ describe("CipherContextMenuHandler", () => { jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue(); - sut = new CipherContextMenuHandler(mainContextMenuHandler, authService, cipherService); + sut = new CipherContextMenuHandler( + mainContextMenuHandler, + authService, + cipherService, + accountService, + ); }); afterEach(() => jest.resetAllMocks()); @@ -119,10 +129,11 @@ describe("CipherContextMenuHandler", () => { expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", [ - CipherType.Card, - CipherType.Identity, - ]); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith( + "https://test.com", + mockUserId, + [CipherType.Card, CipherType.Identity], + ); expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(3); diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts index b112ff00efe..038f4e85c9a 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts @@ -1,5 +1,9 @@ +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -14,6 +18,7 @@ export class CipherContextMenuHandler { private mainContextMenuHandler: MainContextMenuHandler, private authService: AuthService, private cipherService: CipherService, + private accountService: AccountService, ) {} async update(url: string) { @@ -35,7 +40,14 @@ export class CipherContextMenuHandler { return; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(url, [ + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return; + } + + const ciphers = await this.cipherService.getAllDecryptedForUrl(url, activeUserId, [ CipherType.Card, CipherType.Identity, ]); diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts index 6ef004f7979..c8cb7e81f72 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts @@ -61,6 +61,8 @@ describe("ContextMenuClickedHandler", () => { return cipherView; }; + const mockUserId = "UserId" as UserId; + let copyToClipboard: CopyToClipboardAction; let generatePasswordToClipboard: GeneratePasswordToClipboardAction; let autofill: AutofillAction; @@ -79,7 +81,7 @@ describe("ContextMenuClickedHandler", () => { autofill = jest.fn, [tab: chrome.tabs.Tab, cipher: CipherView]>(); authService = mock(); cipherService = mock(); - accountService = mockAccountServiceWith("userId" as UserId); + accountService = mockAccountServiceWith(mockUserId as UserId); totpService = mock(); eventCollectionService = mock(); @@ -191,7 +193,11 @@ describe("ContextMenuClickedHandler", () => { expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith( + "https://test.com", + mockUserId, + [], + ); expect(copyToClipboard).toHaveBeenCalledTimes(1); @@ -215,7 +221,11 @@ describe("ContextMenuClickedHandler", () => { expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith( + "https://test.com", + mockUserId, + [], + ); }); }); }); diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts index 597d75575b0..69c8b6e70b8 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { AUTOFILL_CARD_ID, AUTOFILL_ID, @@ -105,6 +106,13 @@ export class ContextMenuClickedHandler { menuItemId as string, ); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return; + } + if (isCreateCipherAction) { // pass; defer to logic below } else if (menuItemId === NOOP_COMMAND_SUFFIX) { @@ -120,12 +128,13 @@ export class ContextMenuClickedHandler { // in scenarios like unlock on autofill const ciphers = await this.cipherService.getAllDecryptedForUrl( tab.url, + activeUserId, additionalCiphersToGet, ); cipher = ciphers[0]; } else { - const ciphers = await this.cipherService.getAllDecrypted(); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); cipher = ciphers.find(({ id }) => id === menuItemId); } @@ -133,9 +142,6 @@ export class ContextMenuClickedHandler { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); await this.accountService.setAccountActivity(activeUserId, new Date()); switch (info.parentMenuItemId) { case AUTOFILL_ID: diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts index 2c22097f3d0..497664542ad 100644 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts +++ b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts @@ -110,6 +110,7 @@ describe("OverlayBackground", () => { i18nService, platformUtilsService, themeStateService, + accountService, ); jest @@ -205,7 +206,7 @@ describe("OverlayBackground", () => { await overlayBackground.updateOverlayCiphers(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId); expect(overlayBackground["cipherService"].sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["overlayLoginCiphers"]).toStrictEqual( new Map([ diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts index 5dfade0f863..d0fad4cd00e 100644 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts @@ -1,7 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants"; @@ -106,6 +107,7 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private themeStateService: ThemeStateService, + private accountService: AccountService, ) {} /** @@ -152,9 +154,13 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface { } this.overlayLoginCiphers = new Map(); - const ciphersViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url)).sort( - (a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b), + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const ciphersViews = ( + await this.cipherService.getAllDecryptedForUrl(currentTab.url, activeUserId) + ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) { this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]); } @@ -660,10 +666,16 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface { cipherView.type = CipherType.Login; cipherView.login = loginView; - await this.cipherService.setAddEditCipherInfo({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + await this.cipherService.setAddEditCipherInfo( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + activeUserId, + ); await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id }); await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher"); diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 4555d87f249..24c14c98685 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -20,6 +20,7 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/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"; @@ -186,7 +187,10 @@ export class Fido2Component implements OnInit, OnDestroy { this.domainSettingsService.getUrlEquivalentDomains(this.url), ); - this.ciphers = (await this.cipherService.getAllDecrypted()).filter( + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ); + this.ciphers = (await this.cipherService.getAllDecrypted(activeUserId)).filter( (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, ); @@ -211,7 +215,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); + const cipher = await this.cipherService.get(cipherId, activeUserId); return cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -232,7 +236,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); + const cipher = await this.cipherService.get(cipherId, activeUserId); return cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 16b11b98866..3843734ad64 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -769,7 +769,10 @@ describe("AutofillService", () => { ); expect(autofillService["generateLoginFillScript"]).toHaveBeenCalled(); expect(logService.info).not.toHaveBeenCalled(); - expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(autofillOptions.cipher.id); + expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith( + autofillOptions.cipher.id, + mockUserId, + ); expect(chrome.tabs.sendMessage).toHaveBeenCalledWith( autofillOptions.pageDetails[0].tab.id, { @@ -1030,8 +1033,8 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, false); expect(cipherService.getNextCipherForUrl).not.toHaveBeenCalled(); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); - expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); + expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); expect(result).toBeNull(); }); @@ -1044,7 +1047,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(cipherService.getLastLaunchedForUrl).not.toHaveBeenCalled(); expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); @@ -1074,7 +1077,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled(); expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ @@ -1104,8 +1107,8 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); - expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); + expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ tab: tab, @@ -1132,7 +1135,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(cipherService.updateLastUsedIndexForUrl).toHaveBeenCalledWith(tab.url); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ tab: tab, @@ -1163,7 +1166,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(userVerificationService.hasMasterPasswordAndMasterKeyHash).toHaveBeenCalled(); expect(autofillService["openVaultItemPasswordRepromptPopout"]).toHaveBeenCalledWith(tab, { cipherId: cipher.id, @@ -1189,7 +1192,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(autofillService["openVaultItemPasswordRepromptPopout"]).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); expect(result).toBeNull(); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 6d0e9954ade..fc7d0ebcc99 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -8,6 +8,7 @@ import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { AutofillOverlayVisibility, CardExpiryDateDelimiters, @@ -464,7 +465,7 @@ export default class AutofillService implements AutofillServiceInterface { didAutofill = true; if (!options.skipLastUsed) { - await this.cipherService.updateLastUsedDate(options.cipher.id); + await this.cipherService.updateLastUsedDate(options.cipher.id, activeAccount.id); } // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -527,17 +528,29 @@ export default class AutofillService implements AutofillServiceInterface { autoSubmitLogin = false, ): Promise { let cipher: CipherView; + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return null; + } + if (fromCommand) { - cipher = await this.cipherService.getNextCipherForUrl(tab.url); + cipher = await this.cipherService.getNextCipherForUrl(tab.url, activeUserId); } else { - const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(tab.url, true); + const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl( + tab.url, + activeUserId, + true, + ); if ( lastLaunchedCipher && Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000 ) { cipher = lastLaunchedCipher; } else { - cipher = await this.cipherService.getLastUsedForUrl(tab.url, true); + cipher = await this.cipherService.getLastUsedForUrl(tab.url, activeUserId, true); } } @@ -626,12 +639,19 @@ export default class AutofillService implements AutofillServiceInterface { let cipher: CipherView; let cacheKey = ""; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId == null) { + return null; + } + if (cipherType === CipherType.Card) { cacheKey = "cardCiphers"; - cipher = await this.cipherService.getNextCardCipher(); + cipher = await this.cipherService.getNextCardCipher(activeUserId); } else { cacheKey = "identityCiphers"; - cipher = await this.cipherService.getNextIdentityCipher(); + cipher = await this.cipherService.getNextIdentityCipher(activeUserId); } if (!cipher || !cacheKey || (cipher.reprompt === CipherRepromptType.Password && !fromCommand)) { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 68fa65f8528..d2b51c7ef40 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1258,6 +1258,7 @@ export default class MainBackground { this.mainContextMenuHandler, this.authService, this.cipherService, + this.accountService, ); if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) { @@ -1265,6 +1266,7 @@ export default class MainBackground { this.platformUtilsService, this.cipherService, this.authService, + this.accountService, chrome.webRequest, ); } @@ -1636,6 +1638,7 @@ export default class MainBackground { this.i18nService, this.platformUtilsService, this.themeStateService, + this.accountService, ); } else { this.overlayBackground = new OverlayBackground( @@ -1653,6 +1656,7 @@ export default class MainBackground { this.inlineMenuFieldQualificationService, this.themeStateService, this.totpService, + this.accountService, () => this.generatePassword(), (password) => this.addPasswordToHistory(password), ); diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index a71de8506ea..e5d5db20758 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.2.0", + "version": "2025.2.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 8da048e04f2..fe3aad37cf4 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.2.0", + "version": "2025.2.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -63,6 +63,22 @@ "webRequestAuthProvider", "notifications" ], + "__firefox__permissions": [ + "activeTab", + "alarms", + "clipboardRead", + "clipboardWrite", + "contextMenus", + "idle", + "scripting", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation", + "webRequest", + "webRequestAuthProvider", + "notifications" + ], "__safari__permissions": [ "activeTab", "alarms", @@ -86,7 +102,7 @@ "host_permissions": ["https://*/*", "http://*/*"], "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", - "sandbox": "sandbox allow-scripts; script-src 'self'" + "__chrome__sandbox": "sandbox allow-scripts; script-src 'self'" }, "sandbox": { "pages": [ @@ -112,6 +128,13 @@ }, "description": "__MSG_commandOpenSidebar__" }, + "__opera___execute_sidebar_action": { + "suggested_key": { + "default": "Alt+Shift+Y", + "linux": "Alt+Shift+U" + }, + "description": "__MSG_commandOpenSidebar__" + }, "autofill_login": { "suggested_key": { "default": "Ctrl+Shift+L" @@ -166,5 +189,12 @@ "storage": { "managed_schema": "managed_schema.json" }, - "__firefox__storage": null + "__firefox__storage": null, + "__opera__sidebar_action": { + "default_title": "Bitwarden", + "default_panel": "popup/index.html?uilocation=sidebar", + "default_icon": "images/icon19.png", + "open_at_install": false, + "browser_style": false + } } diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 1d8ff65c17d..293fca4d029 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -530,10 +530,15 @@ export class BrowserApi { win: Window & typeof globalThis, ): OperaSidebarAction | FirefoxSidebarAction | null { const deviceType = BrowserPlatformUtilsService.getDevice(win); - if (deviceType !== DeviceType.FirefoxExtension && deviceType !== DeviceType.OperaExtension) { - return null; + if (deviceType === DeviceType.FirefoxExtension) { + return browser.sidebarAction; } - return win.opr?.sidebarAction || browser.sidebarAction; + + if (deviceType === DeviceType.OperaExtension) { + return win.opr?.sidebarAction; + } + + return null; } static captureVisibleTab(): Promise { diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index f67b96c847f..cd74b9ebf7d 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -2,8 +2,10 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -22,6 +24,7 @@ export class UpdateBadge { private authService: AuthService; private badgeSettingsService: BadgeSettingsServiceAbstraction; private cipherService: CipherService; + private accountService: AccountService; private badgeAction: typeof chrome.action | typeof chrome.browserAction; private sidebarAction: OperaSidebarAction | FirefoxSidebarAction; private win: Window & typeof globalThis; @@ -34,6 +37,7 @@ export class UpdateBadge { this.badgeSettingsService = services.badgeSettingsService; this.authService = services.authService; this.cipherService = services.cipherService; + this.accountService = services.accountService; } async run(opts?: { tabId?: number; windowId?: number }): Promise { @@ -87,7 +91,14 @@ export class UpdateBadge { return; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (!activeUserId) { + return; + } + + const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url, activeUserId); let countText = ciphers.length == 0 ? "" : ciphers.length.toString(); if (ciphers.length > 9) { countText = "9+"; @@ -165,6 +176,13 @@ export class UpdateBadge { return; } + if ("opr" in this.win && BrowserApi.isManifestVersion(3)) { + // setIcon API is currenly broken for Opera MV3 extensions + // https://forums.opera.com/topic/75680/opr-sidebaraction-seticon-api-is-broken-access-to-extension-api-denied?_=1738349261570 + // The API currently crashes on MacOS + return; + } + if (this.isOperaSidebar(this.sidebarAction)) { await new Promise((resolve) => (this.sidebarAction as OperaSidebarAction).setIcon(options, () => resolve()), diff --git a/apps/browser/src/platform/popup/layout/popup-layout.mdx b/apps/browser/src/platform/popup/layout/popup-layout.mdx index 5723bef44b1..b93c649d695 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.mdx +++ b/apps/browser/src/platform/popup/layout/popup-layout.mdx @@ -100,9 +100,7 @@ Usage example: ### Transparent header - - - + Common interactive elements to insert into the `end` slot are: @@ -113,9 +111,7 @@ Common interactive elements to insert into the `end` slot are: ### Notice - - - + Common interactive elements to insert into the `full-width-notice` slot are: @@ -160,29 +156,21 @@ View the story source code to see examples of how to construct these types of pa Example of wrapping an extension page in the `popup-tab-navigation` component. - - - + ## Extension Page Examples of using just the `popup-page` component, without and with a footer. - - - + - - - + ## Popped out When the browser extension is popped out, the "popout" button should not be passed to the header. - - - + # Other stories @@ -190,14 +178,10 @@ When the browser extension is popped out, the "popout" button should not be pass An example of how to center the default content. - - - + ## Loading An example of what the loading state looks like. - - - + diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 70a78ce548f..d55a64a64eb 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -64,7 +64,6 @@ import { HomeComponent } from "../auth/popup/home.component"; import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "../auth/popup/login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component"; -import { RegisterComponent } from "../auth/popup/register.component"; import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; @@ -267,12 +266,6 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, - { - path: "register", - component: RegisterComponent, - canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { elevation: 1 } satisfies RouteDataProperties, - }, { path: "environment", component: EnvironmentComponent, diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 8147524d2f3..a97d001c54b 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -179,7 +179,7 @@ export class AppComponent implements OnInit, OnDestroy { await this.clearComponentStates(); } if (url.startsWith("/tabs/")) { - await this.cipherService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null, this.activeUserId); } (window as any).previousPopupUrl = url; diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 15a898aef53..3046ec7916a 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -26,7 +26,6 @@ import { HomeComponent } from "../auth/popup/home.component"; import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "../auth/popup/login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component"; -import { RegisterComponent } from "../auth/popup/register.component"; import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; @@ -103,7 +102,6 @@ import "../platform/popup/locales"; LoginViaAuthRequestComponentV1, LoginComponentV1, LoginDecryptionOptionsComponentV1, - RegisterComponent, SetPasswordComponent, SsoComponentV1, TabsV2Component, 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 index 3252f030fc3..6974e6f7359 100644 --- 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 @@ -1,14 +1,17 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; import { ActivatedRoute, Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, Observable } from "rxjs"; +import { BehaviorSubject } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -58,9 +61,9 @@ describe("AddEditV2Component", () => { collect.mockClear(); addEditCipherInfo$ = new BehaviorSubject(null); - cipherServiceMock = mock(); - cipherServiceMock.addEditCipherInfo$ = - addEditCipherInfo$.asObservable() as Observable; + cipherServiceMock = mock({ + addEditCipherInfo$: jest.fn().mockReturnValue(addEditCipherInfo$), + }); await TestBed.configureTestingModule({ imports: [AddEditV2Component], @@ -81,6 +84,7 @@ describe("AddEditV2Component", () => { canDeleteCipher$: jest.fn().mockReturnValue(true), }, }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }) .overrideProvider(CipherFormConfigService, { 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 b46b1d61509..1dcb48c918d 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 @@ -9,10 +9,12 @@ import { firstValueFrom, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId, 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"; @@ -180,6 +182,7 @@ export class AddEditV2Component implements OnInit { private toastService: ToastService, private dialogService: DialogService, protected cipherAuthorizationService: CipherAuthorizationService, + private accountService: AccountService, ) { this.subscribeToParams(); } @@ -281,9 +284,15 @@ export class AddEditV2Component implements OnInit { config.initialValues = this.setInitialValuesFromParams(params); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ); + // The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form // Attempt to fetch them here and overwrite the initialValues if present - const cachedCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$); + const cachedCipherInfo = await firstValueFrom( + this.cipherService.addEditCipherInfo$(activeUserId), + ); if (cachedCipherInfo != null) { // Cached cipher info has priority over queryParams @@ -292,7 +301,7 @@ export class AddEditV2Component implements OnInit { ...mapAddEditCipherInfoToInitialValues(cachedCipherInfo), }; // Be sure to clear the "cached" cipher info, so it doesn't get used again - await this.cipherService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null, activeUserId); } if (["edit", "partial-edit"].includes(config.mode) && config.originalCipher?.id) { @@ -371,7 +380,8 @@ export class AddEditV2Component implements OnInit { } try { - await this.deleteCipher(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.deleteCipher(activeUserId); } catch (e) { this.logService.error(e); return false; @@ -388,10 +398,10 @@ export class AddEditV2Component implements OnInit { return true; }; - protected deleteCipher() { + protected deleteCipher(userId: UserId) { return this.config.originalCipher.deletedDate - ? this.cipherService.deleteWithServer(this.config.originalCipher.id) - : this.cipherService.softDeleteWithServer(this.config.originalCipher.id); + ? this.cipherService.deleteWithServer(this.config.originalCipher.id, userId) + : this.cipherService.softDeleteWithServer(this.config.originalCipher.id, userId); } } 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 51ebe9bdb62..27f3b7e5e18 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 @@ -5,12 +5,13 @@ 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, map, switchMap } from "rxjs"; +import { Observable, combineLatest, filter, first, map, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -58,16 +59,19 @@ export class AssignCollections { private accountService: AccountService, route: ActivatedRoute, ) { - const cipher$: Observable = route.queryParams.pipe( - switchMap(({ cipherId }) => this.cipherService.get(cipherId)), - switchMap((cipherDomain) => - this.accountService.activeAccount$.pipe( - map((account) => account?.id), - switchMap((userId) => - this.cipherService - .getKeyForCipherKeyDecryption(cipherDomain, userId) - .then(cipherDomain.decrypt.bind(cipherDomain)), - ), + const cipher$: Observable = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + filter((userId) => userId != null), + switchMap((userId) => + route.queryParams.pipe( + switchMap(async ({ cipherId }) => { + const cipherDomain = await this.cipherService.get(cipherId, userId); + const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption( + cipherDomain, + userId, + ); + return cipherDomain.decrypt(key); + }), ), ), ); 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 aca494716b1..1bc7e22e6d5 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 @@ -77,10 +77,10 @@ export class OpenAttachmentsComponent implements OnInit { return; } - const cipherDomain = await this.cipherService.get(this.cipherId); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); const cipher = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts index 1f67dd51c21..fb5ac4b3391 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts @@ -59,7 +59,9 @@ describe("VaultHeaderV2Component", () => { providers: [ { provide: CipherService, - useValue: mock({ cipherViews$: new BehaviorSubject([]) }), + useValue: mock({ + cipherViews$: jest.fn().mockReturnValue(new BehaviorSubject([])), + }), }, { provide: VaultSettingsService, useValue: mock() }, { provide: FolderService, useValue: mock() }, 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 f95790cda5f..cb758e7a48d 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 @@ -22,6 +22,8 @@ import { Router } from "@angular/router"; import { firstValueFrom, Observable, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherId } from "@bitwarden/common/types/guid"; @@ -265,6 +267,7 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { private router: Router, private platformUtilsService: PlatformUtilsService, private dialogService: DialogService, + private accountService: AccountService, ) {} ngOnInit(): void { @@ -311,7 +314,8 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { this.viewCipherTimeout = null; } - await this.cipherService.updateLastLaunchedDate(cipher.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); await BrowserApi.createNewTab(cipher.login.launchUri); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts index 9ac17b49386..838ce2e9426 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts @@ -1,13 +1,15 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; import { ActivatedRoute } from "@angular/router"; import { mock } from "jest-mock-extended"; -import { BehaviorSubject, Subject } from "rxjs"; +import { Subject } from "rxjs"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -19,6 +21,7 @@ import { PasswordHistoryV2Component } from "./vault-password-history-v2.componen describe("PasswordHistoryV2Component", () => { let fixture: ComponentFixture; const params$ = new Subject(); + const mockUserId = "acct-1" as UserId; const mockCipherView = { id: "111-222-333", @@ -45,9 +48,7 @@ describe("PasswordHistoryV2Component", () => { { provide: CipherService, useValue: mock({ get: getCipher }) }, { provide: AccountService, - useValue: mock({ - activeAccount$: new BehaviorSubject({ id: "acct-1" } as Account), - }), + useValue: mockAccountServiceWith(mockUserId), }, { provide: PopupRouterCacheService, useValue: { back } }, { provide: ActivatedRoute, useValue: { queryParams: params$ } }, @@ -64,7 +65,7 @@ describe("PasswordHistoryV2Component", () => { tick(100); - expect(getCipher).toHaveBeenCalledWith(mockCipherView.id); + expect(getCipher).toHaveBeenCalledWith(mockCipherView.id, mockUserId); })); it("navigates back when a cipherId is not in the params", () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index e20bb1f1bcd..5d315775b10 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -58,8 +58,6 @@ export class PasswordHistoryV2Component implements OnInit { /** Load the cipher based on the given Id */ private async loadCipher(cipherId: string) { - const cipher = await this.cipherService.get(cipherId); - const activeAccount = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)), ); @@ -69,6 +67,8 @@ export class PasswordHistoryV2Component implements OnInit { } const activeUserId = activeAccount.id as UserId; + + const cipher = await this.cipherService.get(cipherId, activeUserId); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 12952a69c79..635ae82fc37 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -3,9 +3,20 @@ import { CommonModule } from "@angular/common"; import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { RouterLink } from "@angular/router"; -import { combineLatest, filter, map, Observable, shareReplay, switchMap, take } from "rxjs"; +import { + combineLatest, + filter, + map, + firstValueFrom, + Observable, + shareReplay, + switchMap, + take, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -96,6 +107,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupListFiltersService: VaultPopupListFiltersService, private vaultScrollPositionService: VaultPopupScrollPositionService, + private accountService: AccountService, private destroyRef: DestroyRef, private cipherService: CipherService, private dialogService: DialogService, @@ -136,7 +148,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { } async ngOnInit() { - this.cipherService.failedToDecryptCiphers$ + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + this.cipherService + .failedToDecryptCiphers$(activeUserId) .pipe( map((ciphers) => ciphers.filter((c) => !c.isDeleted)), filter((ciphers) => ciphers.length > 0), 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 39feb86f4fd..17464c025af 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 @@ -150,7 +150,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444"); + expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444", mockUserId); expect(component.cipher).toEqual(mockCipher); })); 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 65fb024ee99..209691869f0 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 @@ -5,13 +5,14 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, map, Observable, switchMap } from "rxjs"; +import { firstValueFrom, Observable, switchMap } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AUTOFILL_ID, COPY_PASSWORD_ID, @@ -22,6 +23,7 @@ import { import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; @@ -87,6 +89,8 @@ type LoadAction = ], }) export class ViewV2Component { + private activeUserId: UserId; + headerText: string; cipher: CipherView; organization$: Observable; @@ -117,14 +121,20 @@ export class ViewV2Component { subscribeToParams(): void { this.route.queryParams .pipe( - switchMap(async (params): Promise => { + switchMap(async (params) => { this.loadAction = params.action; this.senderTabId = params.senderTabId ? parseInt(params.senderTabId, 10) : undefined; - return await this.getCipherData(params.cipherId); + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ); + const cipher = await this.getCipherData(params.cipherId, activeUserId); + return { activeUserId, cipher }; }), - switchMap(async (cipher) => { + switchMap(async ({ activeUserId, cipher }) => { this.cipher = cipher; this.headerText = this.setHeader(cipher.type); + this.activeUserId = activeUserId; if (this.loadAction) { await this._handleLoadAction(this.loadAction, this.senderTabId); @@ -159,13 +169,10 @@ export class ViewV2Component { } } - async getCipherData(id: string) { - const cipher = await this.cipherService.get(id); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + async getCipherData(id: string, userId: UserId) { + const cipher = await this.cipherService.get(id, userId); return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), ); } @@ -213,7 +220,7 @@ export class ViewV2Component { restore = async (): Promise => { try { - await this.cipherService.restoreWithServer(this.cipher.id); + await this.cipherService.restoreWithServer(this.cipher.id, this.activeUserId); } catch (e) { this.logService.error(e); } @@ -228,8 +235,8 @@ export class ViewV2Component { protected deleteCipher() { return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id) - : this.cipherService.softDeleteWithServer(this.cipher.id); + ? this.cipherService.deleteWithServer(this.cipher.id, this.activeUserId) + : this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId); } protected showFooter(): boolean { 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 ec20458ca60..6d7b7b57d23 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -15,6 +15,8 @@ import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; +import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service"; @@ -34,6 +36,10 @@ describe("VaultPopupItemsService", () => { let mockCollections: CollectionView[]; let activeUserLastSync$: BehaviorSubject; + let ciphersSubject: BehaviorSubject>; + let localDataSubject: BehaviorSubject>; + let failedToDecryptCiphersSubject: BehaviorSubject; + const cipherServiceMock = mock(); const vaultSettingsServiceMock = mock(); const organizationServiceMock = mock(); @@ -60,9 +66,21 @@ describe("VaultPopupItemsService", () => { cipherList[3].favorite = true; cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList); - cipherServiceMock.ciphers$ = new BehaviorSubject(null); - cipherServiceMock.localData$ = new BehaviorSubject(null); - cipherServiceMock.failedToDecryptCiphers$ = new BehaviorSubject([]); + + ciphersSubject = new BehaviorSubject>({}); + localDataSubject = new BehaviorSubject>({}); + failedToDecryptCiphersSubject = new BehaviorSubject([]); + + cipherServiceMock.ciphers$.mockImplementation((userId: UserId) => + ciphersSubject.asObservable(), + ); + cipherServiceMock.localData$.mockImplementation((userId: UserId) => + localDataSubject.asObservable(), + ); + cipherServiceMock.failedToDecryptCiphers$.mockImplementation((userId: UserId) => + failedToDecryptCiphersSubject.asObservable(), + ); + searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers); cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) => ciphers.filter((c) => ["0", "1"].includes(c.id)), @@ -118,6 +136,7 @@ describe("VaultPopupItemsService", () => { { provide: CollectionService, useValue: collectionService }, { provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock }, { provide: SyncService, useValue: syncServiceMock }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, { provide: InlineMenuFieldQualificationService, useValue: inlineMenuFieldQualificationServiceMock, @@ -155,7 +174,7 @@ describe("VaultPopupItemsService", () => { await tracker.expectEmission(); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); await tracker.expectEmission(); @@ -169,7 +188,7 @@ describe("VaultPopupItemsService", () => { await tracker.expectEmission(); - (cipherServiceMock.localData$ as BehaviorSubject).next(null); + localDataSubject.next({}); await tracker.expectEmission(); @@ -373,7 +392,7 @@ describe("VaultPopupItemsService", () => { cipherServiceMock.getAllDecrypted.mockResolvedValue(ciphers); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); const deletedCiphers = await firstValueFrom(service.deletedCiphers$); expect(deletedCiphers.length).toBe(1); @@ -422,7 +441,7 @@ describe("VaultPopupItemsService", () => { it("should cycle when cipherService.ciphers$ emits", async () => { // Restart tracking tracked = new ObservableTracker(service.loading$); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); await trackedCiphers.pauseUntilReceived(2); 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 8e0711abb1e..0b3e7eba492 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 @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { inject, Injectable, NgZone } from "@angular/core"; +import { Injectable, NgZone } from "@angular/core"; import { BehaviorSubject, combineLatest, @@ -86,16 +86,19 @@ export class VaultPopupItemsService { * Observable that contains the list of all decrypted ciphers. * @private */ - private _allDecryptedCiphers$: Observable = merge( - this.cipherService.ciphers$, - this.cipherService.localData$, - ).pipe( - runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular - tap(() => this._ciphersLoading$.next()), - waitUntilSync(this.syncService), - switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), - withLatestFrom(this.cipherService.failedToDecryptCiphers$), - map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]), + private _allDecryptedCiphers$: Observable = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + filter((userId) => userId != null), + switchMap((userId) => + merge(this.cipherService.ciphers$(userId), this.cipherService.localData$(userId)).pipe( + runInsideAngular(this.ngZone), + tap(() => this._ciphersLoading$.next()), + waitUntilSync(this.syncService), + switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted(userId))), + withLatestFrom(this.cipherService.failedToDecryptCiphers$(userId)), + map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]), + ), + ), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -281,6 +284,7 @@ export class VaultPopupItemsService { private vaultPopupAutofillService: VaultPopupAutofillService, private syncService: SyncService, private accountService: AccountService, + private ngZone: NgZone, ) {} applyFilter(newSearchText: string) { diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 7f570e8f5c9..ec823d5738f 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -41,7 +41,7 @@ describe("VaultPopupListFiltersService", () => { } as unknown as FolderService; const cipherService = { - cipherViews$, + cipherViews$: () => cipherViews$, } as unknown as CipherService; const organizationService = { diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index 579319c92ab..b1451318499 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -93,16 +93,6 @@ export class VaultPopupListFiltersService { */ private cipherViews: CipherView[] = []; - /** - * Observable of cipher views - */ - private cipherViews$: Observable = this.cipherService.cipherViews$.pipe( - tap((cipherViews) => { - this.cipherViews = Object.values(cipherViews); - }), - map((ciphers) => Object.values(ciphers)), - ); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); constructor( @@ -271,8 +261,16 @@ export class VaultPopupListFiltersService { * Folder array structured to be directly passed to `ChipSelectComponent` */ folders$: Observable[]> = this.activeUserId$.pipe( - switchMap((userId) => - combineLatest([ + switchMap((userId) => { + // Observable of cipher views + const cipherViews$ = this.cipherService.cipherViews$(userId).pipe( + tap((cipherViews) => { + this.cipherViews = Object.values(cipherViews); + }), + map((ciphers) => Object.values(ciphers)), + ); + + return combineLatest([ this.filters$.pipe( distinctUntilChanged( (previousFilter, currentFilter) => @@ -281,7 +279,7 @@ export class VaultPopupListFiltersService { ), ), this.folderService.folderViews$(userId), - this.cipherViews$, + cipherViews$, ]).pipe( map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { if (folders.length === 1 && folders[0].id === null) { @@ -330,8 +328,8 @@ export class VaultPopupListFiltersService { map((folders) => folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), ), - ), - ), + ); + }), ); /** diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index c56d1c7d10d..7e30ab26035 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -3,8 +3,11 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/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"; @@ -65,6 +68,7 @@ export class TrashListItemsContainerComponent { private i18nService: I18nService, private dialogService: DialogService, private passwordRepromptService: PasswordRepromptService, + private accountService: AccountService, private router: Router, ) {} @@ -81,7 +85,8 @@ export class TrashListItemsContainerComponent { async restore(cipher: CipherView) { try { - await this.cipherService.restoreWithServer(cipher.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.restoreWithServer(cipher.id, activeUserId); await this.router.navigate(["/trash"]); this.toastService.showToast({ @@ -112,7 +117,8 @@ export class TrashListItemsContainerComponent { } try { - await this.cipherService.deleteWithServer(cipher.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.deleteWithServer(cipher.id, activeUserId); await this.router.navigate(["/trash"]); this.toastService.showToast({ diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index ba60a577a71..d077e54bb73 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -317,19 +317,24 @@ if (manifestVersion == 2) { configs.push(mainConfig); } else { - // Manifest v3 needs an extra helper for utilities in the content script. - // The javascript output of this should be added to manifest.v3.json - mainConfig.entry["content/misc-utils"] = "./src/autofill/content/misc-utils.ts"; - mainConfig.entry["offscreen-document/offscreen-document"] = - "./src/platform/offscreen-document/offscreen-document.ts"; + // Firefox does not use the offscreen API + if (browser !== "firefox") { + // Manifest v3 needs an extra helper for utilities in the content script. + // The javascript output of this should be added to manifest.v3.json + mainConfig.entry["content/misc-utils"] = "./src/autofill/content/misc-utils.ts"; + mainConfig.entry["offscreen-document/offscreen-document"] = + "./src/platform/offscreen-document/offscreen-document.ts"; - mainConfig.plugins.push( - new HtmlWebpackPlugin({ - template: "./src/platform/offscreen-document/index.html", - filename: "offscreen-document/index.html", - chunks: ["offscreen-document/offscreen-document"], - }), - ); + mainConfig.plugins.push( + new HtmlWebpackPlugin({ + template: "./src/platform/offscreen-document/index.html", + filename: "offscreen-document/index.html", + chunks: ["offscreen-document/offscreen-document"], + }), + ); + } + + const target = browser === "firefox" ? "web" : "webworker"; /** * @type {import("webpack").Configuration} @@ -339,7 +344,7 @@ if (manifestVersion == 2) { mode: ENV, devtool: false, entry: "./src/platform/background.ts", - target: "webworker", + target: target, output: { filename: "background.js", path: path.resolve(__dirname, "build"), diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index e26d073326e..6d9e6c8b6c0 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -1,8 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Response } from "../../models/response"; @@ -48,7 +49,9 @@ export class ShareCommand { organizationId = organizationId.toLowerCase(); } - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -56,15 +59,12 @@ export class ShareCommand { 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, activeUserId), ); try { await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); - const updatedCipher = await this.cipherService.get(cipher.id); + const updatedCipher = await this.cipherService.get(cipher.id, activeUserId); const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 9af28863c09..2d4a854135d 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -1,11 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { CollectionRequest } from "@bitwarden/admin-console/common"; 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 { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; @@ -25,8 +26,6 @@ import { CipherResponse } from "../vault/models/cipher.response"; import { FolderResponse } from "../vault/models/folder.response"; export class EditCommand { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private folderService: FolderService, @@ -85,14 +84,12 @@ export class EditCommand { } private async editCipher(id: string, req: CipherExport) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { 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, activeUserId), ); @@ -114,7 +111,9 @@ export class EditCommand { } private async editCipherCollections(id: string, req: string[]) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -129,11 +128,14 @@ export class EditCommand { cipher.collectionIds = req; try { - const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher); + const updatedCipher = await this.cipherService.saveCollectionsWithServer( + cipher, + activeUserId, + ); const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption( updatedCipher, - await firstValueFrom(this.activeUserId$), + await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), ), ); const res = new CipherResponse(decCipher); @@ -144,7 +146,7 @@ export class EditCommand { } private async editFolder(id: string, req: FolderExport) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index a90bfa64894..92c3a8baeaf 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -52,8 +52,6 @@ import { FolderResponse } from "../vault/models/folder.response"; import { DownloadCommand } from "./download.command"; export class GetCommand extends DownloadCommand { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private folderService: FolderService, @@ -114,16 +112,16 @@ export class GetCommand extends DownloadCommand { private async getCipherView(id: string): Promise { let decCipher: CipherView = null; + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); if (Utils.isGuid(id)) { - const cipher = await this.cipherService.get(id); + const cipher = await this.cipherService.get(id, activeUserId); if (cipher != null) { - const activeUserId = await firstValueFrom(this.activeUserId$); decCipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); } } else if (id.trim() !== "") { - let ciphers = await this.cipherService.getAllDecrypted(); + let ciphers = await this.cipherService.getAllDecrypted(activeUserId); ciphers = this.searchService.searchCiphersBasic(ciphers, id); if (ciphers.length > 1) { return ciphers; @@ -265,8 +263,10 @@ export class GetCommand extends DownloadCommand { const canAccessPremium = await firstValueFrom( this.accountProfileService.hasPremiumFromAnySource$(account.id), ); + if (!canAccessPremium) { - const originalCipher = await this.cipherService.get(cipher.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if ( originalCipher == null || originalCipher.organizationId == null || @@ -352,7 +352,8 @@ export class GetCommand extends DownloadCommand { this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (!canAccessPremium) { - const originalCipher = await this.cipherService.get(cipher.id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if (originalCipher == null || originalCipher.organizationId == null) { return Response.error("Premium status is required to use this feature."); } @@ -384,7 +385,7 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); if (Utils.isGuid(id)) { const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { @@ -561,7 +562,7 @@ export class GetCommand extends DownloadCommand { private async getFingerprint(id: string) { let fingerprint: string[] = null; if (id === "me") { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); } else if (Utils.isGuid(id)) { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 5e01af798a4..5d512d81bf5 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -65,11 +65,14 @@ export class ListCommand { private async listCiphers(options: Options) { let ciphers: CipherView[]; + + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + options.trash = options.trash || false; if (options.url != null && options.url.trim() !== "") { - ciphers = await this.cipherService.getAllDecryptedForUrl(options.url); + ciphers = await this.cipherService.getAllDecryptedForUrl(options.url, activeUserId); } else { - ciphers = await this.cipherService.getAllDecrypted(); + ciphers = await this.cipherService.getAllDecrypted(activeUserId); } if ( @@ -138,9 +141,8 @@ export class ListCommand { } private async listFolders(options: Options) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); if (options.search != null && options.search.trim() !== "") { diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 96179e7b687..fd2f6f239ef 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -1,9 +1,16 @@ +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Response } from "../models/response"; export class RestoreCommand { - constructor(private cipherService: CipherService) {} + constructor( + private cipherService: CipherService, + private accountService: AccountService, + ) {} async run(object: string, id: string): Promise { if (id != null) { @@ -19,7 +26,9 @@ export class RestoreCommand { } private async restoreCipher(id: string) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -28,7 +37,7 @@ export class RestoreCommand { } try { - await this.cipherService.restoreWithServer(id); + await this.cipherService.restoreWithServer(id, activeUserId); return Response.success(); } catch (e) { return Response.error(e); diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index be476d19814..dec09447839 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -124,7 +124,10 @@ export class OssServeConfigurator { this.serviceContainer.encryptService, this.serviceContainer.organizationUserApiService, ); - this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService); + this.restoreCommand = new RestoreCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); this.shareCommand = new ShareCommand( this.serviceContainer.cipherService, this.serviceContainer.accountService, diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index f3eb6eef613..d6ef27b1428 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -347,7 +347,10 @@ export class VaultProgram extends BaseProgram { } await this.exitIfLocked(); - const command = new RestoreCommand(this.serviceContainer.cipherService); + const command = new RestoreCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); const response = await command.run(object, id); this.processResponse(response); }); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 28f58187fdb..713471356c9 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -10,6 +10,7 @@ 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 { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; @@ -30,8 +31,6 @@ import { CipherResponse } from "./models/cipher.response"; import { FolderResponse } from "./models/folder.response"; export class CreateCommand { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private folderService: FolderService, @@ -90,7 +89,7 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); @@ -132,14 +131,14 @@ export class CreateCommand { return Response.badRequest("File name not provided."); } + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const itemId = options.itemId.toLowerCase(); - const cipher = await this.cipherService.get(itemId); + const cipher = await this.cipherService.get(itemId, activeUserId); if (cipher == null) { return Response.notFound(); } - const activeUserId = await firstValueFrom(this.activeUserId$); - const canAccessPremium = await firstValueFrom( this.accountProfileService.hasPremiumFromAnySource$(activeUserId), ); @@ -173,7 +172,7 @@ export class CreateCommand { } private async createFolder(req: FolderExport) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index a285f8f5b34..9e648cd9bb0 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -1,7 +1,8 @@ -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -44,7 +45,9 @@ export class DeleteCommand { } private async deleteCipher(id: string, options: Options) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -59,9 +62,9 @@ export class DeleteCommand { try { if (options.permanent) { - await this.cipherService.deleteWithServer(id); + await this.cipherService.deleteWithServer(id, activeUserId); } else { - await this.cipherService.softDeleteWithServer(id); + await this.cipherService.softDeleteWithServer(id, activeUserId); } return Response.success(); } catch (e) { @@ -74,8 +77,10 @@ export class DeleteCommand { return Response.badRequest("`itemid` option is required."); } + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const itemId = options.itemId.toLowerCase(); - const cipher = await this.cipherService.get(itemId); + const cipher = await this.cipherService.get(itemId, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -89,16 +94,19 @@ export class DeleteCommand { return Response.error("Attachment `" + id + "` was not found."); } - const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$(account.id), + this.accountProfileService.hasPremiumFromAnySource$(activeUserId), ); if (cipher.organizationId == null && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); } try { - await this.cipherService.deleteAttachmentWithServer(cipher.id, attachments[0].id); + await this.cipherService.deleteAttachmentWithServer( + cipher.id, + attachments[0].id, + activeUserId, + ); return Response.success(); } catch (e) { return Response.error(e); @@ -106,9 +114,7 @@ export class DeleteCommand { } private async deleteFolder(id: string) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 709506f576f..a47f938efd2 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -436,6 +436,23 @@ "enableSshAgentDesc" | i18n }} +
+
+ +
+ {{ + "allowScreenshotsDesc" | i18n + }} +
-
-
- {{ "verificationCodeTotp" | i18n }} - - {{ "organizationUpgradeRequired" | i18n }} - - -
-
diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 136a537dd37..aee1f34437b 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -159,10 +159,4 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.messagingService.send("premiumRequired"); } } - - upgradeOrganization() { - this.messagingService.send("upgradeOrganization", { - organizationId: this.cipher.organizationId, - }); - } } diff --git a/apps/web/package.json b/apps/web/package.json index 6f402f737a9..cf2df649773 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.2.0", + "version": "2025.2.1", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts index 2a972198cc5..5601c2f141f 100644 --- a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -11,6 +11,7 @@ import { OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -59,15 +60,13 @@ export class ExposedPasswordsReportComponent this.isAdminConsoleActive = true; // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.organization = await firstValueFrom( this.organizationService .organizations$(userId) .pipe(getOrganizationById(params.organizationId)), ); - this.manageableCiphers = await this.cipherService.getAll(); + this.manageableCiphers = await this.cipherService.getAll(userId); }); } diff --git a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts index 3609a9fe146..19f5d3607fa 100644 --- a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { @@ -10,6 +10,7 @@ import { OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -56,15 +57,13 @@ export class ReusedPasswordsReportComponent this.isAdminConsoleActive = true; // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.organization = await firstValueFrom( this.organizationService .organizations$(userId) .pipe(getOrganizationById(params.organizationId)), ); - this.manageableCiphers = await this.cipherService.getAll(); + this.manageableCiphers = await this.cipherService.getAll(userId); await super.ngOnInit(); }); } diff --git a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts index 2bd6285d4f3..7c1c71b934d 100644 --- a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { @@ -10,6 +10,7 @@ import { OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -59,15 +60,14 @@ export class WeakPasswordsReportComponent this.isAdminConsoleActive = true; // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.organization = await firstValueFrom( this.organizationService .organizations$(userId) .pipe(getOrganizationById(params.organizationId)), ); - this.manageableCiphers = await this.cipherService.getAll(); + this.manageableCiphers = await this.cipherService.getAll(userId); await super.ngOnInit(); }); } diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index dd5c56f9b91..eb98f7fde07 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -88,7 +88,9 @@ export class ChangePasswordComponent async rotateUserKeyClicked() { if (this.rotateUserKey) { - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); let hasOldAttachments = false; if (ciphers != null) { for (let i = 0; i < ciphers.length; i++) { diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html index fc199a55a76..524ebbc28cf 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html @@ -146,7 +146,7 @@

{{ "noTrustedContacts" | i18n }}

@@ -263,7 +263,7 @@

{{ "noGrantedAccess" | i18n }}

diff --git a/apps/web/src/app/auth/settings/security/device-management.component.html b/apps/web/src/app/auth/settings/security/device-management.component.html index 2b8d8be3d0f..c38283cfd80 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.html +++ b/apps/web/src/app/auth/settings/security/device-management.component.html @@ -1,7 +1,7 @@ -
+
-

{{ "devices" | i18n }}

+

{{ "devices" | i18n }}

``` - + Note that if the user resizes the page and the Popover no longer fits in the viewport, the Popover component will fall back to the list of default positions to find the best position. diff --git a/libs/components/src/progress/progress.mdx b/libs/components/src/progress/progress.mdx index 185d6214f1f..9a75f8ae1fa 100644 --- a/libs/components/src/progress/progress.mdx +++ b/libs/components/src/progress/progress.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./progress.stories"; @@ -19,14 +19,14 @@ allows those who may not be familiar with the pattern to be able to read and dig It also allows assistive technology to accurately describe the indicator to those who may be unable to see part or all of the indicator. - + ## Text label When measuring something other than progress, such as password strength, update the label to fit the context of the implementation. - + ### Strength indicator styles diff --git a/libs/components/src/section/section.mdx b/libs/components/src/section/section.mdx index 52672ad59fb..92798420479 100644 --- a/libs/components/src/section/section.mdx +++ b/libs/components/src/section/section.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs"; +import { Meta, Primary, Controls, Canvas } from "@storybook/addon-docs"; import * as stories from "./section.stories"; @@ -26,9 +26,7 @@ the `disableMargin` input. ``` - - - + ## Section Header @@ -61,16 +59,12 @@ padding to align the header with the border radius of the card/item. ``` - - - + If placed inside of a section without a `bit-card` or `bit-item`, or with a `bit-card`/`bit-item` that is not a descendant of the immediate next sibling, the padding is not applied. - - - + ### Section Header Content Slots @@ -89,14 +83,10 @@ Anything passed to the default slot will display as part of the title. The title Title suffixes (typically an icon or icon button) can be added as well. A gap is automatically applied between the children of the default slot. - - - + #### End slot The `end` slot will typically be used for text or an icon button. - - - + diff --git a/libs/components/src/stories/compact-mode.mdx b/libs/components/src/stories/compact-mode.mdx index 800bc34cf6a..3cffd45847a 100644 --- a/libs/components/src/stories/compact-mode.mdx +++ b/libs/components/src/stories/compact-mode.mdx @@ -1,4 +1,4 @@ -import { Meta, Story } from "@storybook/addon-docs"; +import { Meta, Story, Canvas } from "@storybook/addon-docs"; import * as itemStories from "../item/item.stories"; import * as popupLayoutStories from "../../../../apps/browser/src/platform/popup/layout/popup-layout.stories"; @@ -42,4 +42,4 @@ However, styling with the Tailwind variant should be used when possible as it is ### [Item](?path=/story/component-library-item--compact-mode) - + diff --git a/libs/components/src/tabs/tabs.mdx b/libs/components/src/tabs/tabs.mdx index 3be98e34257..bab5fbb52c4 100644 --- a/libs/components/src/tabs/tabs.mdx +++ b/libs/components/src/tabs/tabs.mdx @@ -1,6 +1,7 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; import * as stories from "./tabs.stories"; +import * as dialogStories from "../dialog/dialog/dialog.stories"; @@ -17,17 +18,17 @@ and 1rem of padding on the left and right. ## Content Tabs - + ## Navigation Tabs - + ## Content tabs in dialogs Tabs can be used in dialogs to separate related content. - + ## Accessibility diff --git a/libs/components/src/typography/typography.mdx b/libs/components/src/typography/typography.mdx index 11b37bb53b4..d8439481dbc 100644 --- a/libs/components/src/typography/typography.mdx +++ b/libs/components/src/typography/typography.mdx @@ -22,4 +22,4 @@ For headings, note that the semantic element and the text style may not always b

Semantically, I am an h2, but I use h5 styles.

``` - + diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index da59c2986b2..3cd9a0dbb21 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -3,31 +3,13 @@ import { Observable } from "rxjs"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; -import { KdfConfig } from "@bitwarden/key-management"; - -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ProfileOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-organization.response"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ProfileProviderOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-provider-organization.response"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ProfileProviderResponse } from "../../../common/src/admin-console/models/response/profile-provider.response"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { KeySuffixOptions, HashPurpose } from "../../../common/src/platform/enums"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { EncryptedString, EncString } from "../../../common/src/platform/models/domain/enc-string"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { SymmetricCryptoKey } from "../../../common/src/platform/models/domain/symmetric-crypto-key"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { OrganizationId, UserId } from "../../../common/src/types/guid"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports +import { ProfileOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-organization.response"; +import { ProfileProviderOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-provider-organization.response"; +import { ProfileProviderResponse } from "@bitwarden/common/admin-console/models/response/profile-provider.response"; +import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums"; +import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { UserKey, MasterKey, @@ -36,7 +18,9 @@ import { CipherKey, UserPrivateKey, UserPublicKey, -} from "../../../common/src/types/key"; +} from "@bitwarden/common/types/key"; + +import { KdfConfig } from "../models/kdf-config"; export class UserPrivateKeyDecryptionFailedError extends Error { constructor() { diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 97ef878a446..a2cc69f2b21 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -1,82 +1,43 @@ import { mock } from "jest-mock-extended"; import { bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs"; +import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; +import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; - -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { PinServiceAbstraction } from "../../auth/src/common/abstractions"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "@bitwarden/common/platform/services/key-state/org-keys.state"; +import { USER_ENCRYPTED_PROVIDER_KEYS } from "@bitwarden/common/platform/services/key-state/provider-keys.state"; +import { + USER_ENCRYPTED_PRIVATE_KEY, + USER_EVER_HAD_USER_KEY, + USER_KEY, +} from "@bitwarden/common/platform/services/key-state/user-key.state"; +import { UserKeyDefinition } from "@bitwarden/common/platform/state"; +import { VAULT_TIMEOUT } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.state"; import { awaitAsync, makeEncString, makeStaticByteArray, makeSymmetricCryptoKey, -} from "../../common/spec"; -import { FakeAccountService, mockAccountServiceWith } from "../../common/spec/fake-account-service"; -import { FakeActiveUserState, FakeSingleUserState } from "../../common/spec/fake-state"; -import { FakeStateProvider } from "../../common/spec/fake-state-provider"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { FakeMasterPasswordService } from "../../common/src/auth/services/master-password/fake-master-password.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { LogService } from "../../common/src/platform/abstractions/log.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { StateService } from "../../common/src/platform/abstractions/state.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { Encrypted } from "../../common/src/platform/interfaces/encrypted"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { Utils } from "../../common/src/platform/misc/utils"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { - USER_ENCRYPTED_PRIVATE_KEY, - USER_EVER_HAD_USER_KEY, - USER_KEY, -} from "../../common/src/platform/services/key-state/user-key.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { UserKeyDefinition } from "../../common/src/platform/state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { CsprngArray } from "../../common/src/types/csprng"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { OrganizationId, UserId } from "../../common/src/types/guid"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { UserKey, MasterKey } from "../../common/src/types/key"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type"; + FakeAccountService, + mockAccountServiceWith, + FakeStateProvider, + FakeActiveUserState, + FakeSingleUserState, +} from "@bitwarden/common/spec"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { UserKey, MasterKey } from "@bitwarden/common/types/key"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { KdfConfigService } from "./abstractions/kdf-config.service"; import { UserPrivateKeyDecryptionFailedError } from "./abstractions/key.service"; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index a6656c81f2d..1a4f9374d0e 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -12,94 +12,37 @@ import { switchMap, } from "rxjs"; +import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; +import { BaseEncryptedOrganizationKey } from "@bitwarden/common/admin-console/models/domain/encrypted-organization-key"; +import { ProfileOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-organization.response"; +import { ProfileProviderOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-provider-organization.response"; +import { ProfileProviderResponse } from "@bitwarden/common/admin-console/models/response/profile-provider.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; - -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { PinServiceAbstraction } from "../../auth/src/common/abstractions"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { EncryptedOrganizationKeyData } from "../../common/src/admin-console/models/data/encrypted-organization-key.data"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { BaseEncryptedOrganizationKey } from "../../common/src/admin-console/models/domain/encrypted-organization-key"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ProfileOrganizationResponse } from "../../common/src/admin-console/models/response/profile-organization.response"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ProfileProviderOrganizationResponse } from "../../common/src/admin-console/models/response/profile-provider-organization.response"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ProfileProviderResponse } from "../../common/src/admin-console/models/response/profile-provider.response"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { AccountService } from "../../common/src/auth/abstractions/account.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { InternalMasterPasswordServiceAbstraction } from "../../common/src/auth/abstractions/master-password.service.abstraction"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { LogService } from "../../common/src/platform/abstractions/log.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { StateService } from "../../common/src/platform/abstractions/state.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { KeySuffixOptions, HashPurpose } from "../../common/src/platform/enums"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { convertValues } from "../../common/src/platform/misc/convert-values"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { Utils } from "../../common/src/platform/misc/utils"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { EFFLongWordList } from "../../common/src/platform/misc/wordlist"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums"; +import { convertValues } from "@bitwarden/common/platform/misc/convert-values"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "@bitwarden/common/platform/services/key-state/org-keys.state"; +import { USER_ENCRYPTED_PROVIDER_KEYS } from "@bitwarden/common/platform/services/key-state/provider-keys.state"; import { USER_ENCRYPTED_PRIVATE_KEY, USER_EVER_HAD_USER_KEY, USER_KEY, -} from "../../common/src/platform/services/key-state/user-key.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ActiveUserState, StateProvider } from "../../common/src/platform/state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { CsprngArray } from "../../common/src/types/csprng"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { OrganizationId, ProviderId, UserId } from "../../common/src/types/guid"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports +} from "@bitwarden/common/platform/services/key-state/user-key.state"; +import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; +import { VAULT_TIMEOUT } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.state"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid"; import { OrgKey, UserKey, @@ -108,10 +51,8 @@ import { CipherKey, UserPrivateKey, UserPublicKey, -} from "../../common/src/types/key"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type"; +} from "@bitwarden/common/types/key"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { KdfConfigService } from "./abstractions/kdf-config.service"; import { diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts index 3110ebad637..2686e950974 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts @@ -103,7 +103,7 @@ export class DefaultUserAsymmetricKeysRegenerationService } // The private isn't decryptable, check to see if we can decrypt something with the userKey. - const userKeyCanDecrypt = await this.userKeyCanDecrypt(userKey); + const userKeyCanDecrypt = await this.userKeyCanDecrypt(userKey, userId); if (userKeyCanDecrypt) { this.logService.info( "[UserAsymmetricKeyRegeneration] User Asymmetric Key decryption failure detected, attempting regeneration.", @@ -155,8 +155,8 @@ export class DefaultUserAsymmetricKeysRegenerationService ); } - private async userKeyCanDecrypt(userKey: UserKey): Promise { - const ciphers = await this.cipherService.getAll(); + private async userKeyCanDecrypt(userKey: UserKey, userId: UserId): Promise { + const ciphers = await this.cipherService.getAll(userId); const cipher = ciphers.find((cipher) => cipher.organizationId == null); if (cipher != null) { diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index d45c39eebbd..1718bd54234 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import * as papa from "papaparse"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -32,8 +33,6 @@ export class IndividualVaultExportService extends BaseVaultExportService implements IndividualVaultExportServiceAbstraction { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private folderService: FolderService, private cipherService: CipherService, @@ -63,7 +62,7 @@ export class IndividualVaultExportService let decFolders: FolderView[] = []; let decCiphers: CipherView[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => { @@ -72,7 +71,7 @@ export class IndividualVaultExportService ); promises.push( - this.cipherService.getAllDecrypted().then((ciphers) => { + this.cipherService.getAllDecrypted(activeUserId).then((ciphers) => { decCiphers = ciphers.filter((f) => f.deletedDate == null); }), ); @@ -90,7 +89,7 @@ export class IndividualVaultExportService let folders: Folder[] = []; let ciphers: Cipher[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => { @@ -99,7 +98,7 @@ export class IndividualVaultExportService ); promises.push( - this.cipherService.getAll().then((c) => { + this.cipherService.getAll(activeUserId).then((c) => { ciphers = c.filter((f) => f.deletedDate == null); }), ); @@ -107,7 +106,7 @@ export class IndividualVaultExportService await Promise.all(promises); const userKey = await this.keyService.getUserKeyWithLegacySupport( - await firstValueFrom(this.activeUserId$), + await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), ); const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey); 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 86c19950b7c..0961347664d 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,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import * as papa from "papaparse"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { CollectionService, @@ -13,6 +13,7 @@ import { import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -94,9 +95,7 @@ export class OrganizationVaultExportService const decCollections: CollectionView[] = []; const decCiphers: CipherView[] = []; const promises = []; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( this.apiService.getOrganizationExport(organizationId).then((exportData) => { @@ -184,6 +183,7 @@ export class OrganizationVaultExportService let allDecCiphers: CipherView[] = []; let decCollections: CollectionView[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( this.collectionService.getAllDecrypted().then(async (collections) => { @@ -192,7 +192,7 @@ export class OrganizationVaultExportService ); promises.push( - this.cipherService.getAllDecrypted().then((ciphers) => { + this.cipherService.getAllDecrypted(activeUserId).then((ciphers) => { allDecCiphers = ciphers; }), ); @@ -216,6 +216,7 @@ export class OrganizationVaultExportService let allCiphers: Cipher[] = []; let encCollections: Collection[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( this.collectionService.getAll().then((collections) => { @@ -224,7 +225,7 @@ export class OrganizationVaultExportService ); promises.push( - this.cipherService.getAll().then((ciphers) => { + this.cipherService.getAll(activeUserId).then((ciphers) => { allCiphers = ciphers; }), ); 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 1ee9a985f5a..ce12ca95e1e 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 @@ -110,7 +110,7 @@ describe("CipherAttachmentsComponent", () => { it("fetches cipherView using `cipherId`", async () => { await component.ngOnInit(); - expect(cipherServiceGet).toHaveBeenCalledWith("5555-444-3333"); + expect(cipherServiceGet).toHaveBeenCalledWith("5555-444-3333", mockUserId); expect(component.cipher).toEqual(cipherView); }); 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 e366cdab3fe..7e26e8afae9 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 @@ -21,10 +21,11 @@ import { ReactiveFormsModule, Validators, } from "@angular/forms"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; @@ -118,10 +119,8 @@ 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.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.cipherDomain = await this.cipherService.get(this.cipherId, this.activeUserId); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), ); diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts index 8e0d4f7a665..4442fa6e75d 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts @@ -2,12 +2,16 @@ 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { DialogService, ToastService } from "@bitwarden/components"; +import { mockAccountServiceWith } from "../../../../../../common/spec"; + import { DeleteAttachmentComponent } from "./delete-attachment.component"; describe("DeleteAttachmentComponent", () => { @@ -42,6 +46,7 @@ describe("DeleteAttachmentComponent", () => { }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: LogService, useValue: mock() }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }) .overrideProvider(DialogService, { @@ -90,7 +95,11 @@ describe("DeleteAttachmentComponent", () => { }); // Called with cipher id and attachment id - expect(deleteAttachmentWithServer).toHaveBeenCalledWith("5555-444-3333", "222-3333-4444"); + expect(deleteAttachmentWithServer).toHaveBeenCalledWith( + "5555-444-3333", + "222-3333-4444", + "UserId", + ); }); it("shows toast message on successful deletion", async () => { diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index b1ada907b1d..d7d6b550665 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -1,7 +1,10 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; 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"; @@ -36,6 +39,7 @@ export class DeleteAttachmentComponent { private cipherService: CipherService, private logService: LogService, private dialogService: DialogService, + private accountService: AccountService, ) {} delete = async () => { @@ -50,7 +54,19 @@ export class DeleteAttachmentComponent { } try { - await this.cipherService.deleteAttachmentWithServer(this.cipherId, this.attachment.id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + + if (activeUserId == null) { + throw new Error("An active user is expected while deleting an attachment."); + } + + await this.cipherService.deleteAttachmentWithServer( + this.cipherId, + this.attachment.id, + activeUserId, + ); this.toastService.showToast({ variant: "success", diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts index 28b13b51c61..b9add41c222 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts @@ -8,6 +8,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -34,14 +35,12 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { private collectionService: CollectionService = inject(CollectionService); private accountService = inject(AccountService); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - async buildConfig( mode: CipherFormMode, cipherId?: CipherId, cipherType?: CipherType, ): Promise { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const [organizations, collections, allowPersonalOwnership, folders, cipher] = await firstValueFrom( @@ -62,7 +61,7 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { ), ), ), - this.getCipher(cipherId), + this.getCipher(activeUserId, cipherId), ]), ); @@ -94,10 +93,10 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { .policyAppliesToActiveUser$(PolicyType.PersonalOwnership) .pipe(map((p) => !p)); - private getCipher(id?: CipherId): Promise { + private getCipher(userId: UserId, id?: CipherId): Promise { if (id == null) { return Promise.resolve(null); } - return this.cipherService.get(id); + return this.cipherService.get(id, userId); } } 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 059214cc185..98286e4bbb2 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,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { inject, Injectable } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/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"; @@ -23,9 +24,7 @@ export class DefaultCipherFormService implements CipherFormService { private apiService: ApiService = inject(ApiService); async decryptCipher(cipher: Cipher): Promise { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -33,9 +32,7 @@ export class DefaultCipherFormService implements CipherFormService { 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 activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const encryptedCipher = await this.cipherService.encrypt( cipher, activeUserId, @@ -90,7 +87,10 @@ export class DefaultCipherFormService implements CipherFormService { // When using an admin config or the cipher was unassigned, update collections as an admin savedCipher = await this.cipherService.saveCollectionsWithServerAdmin(encryptedCipher); } else { - savedCipher = await this.cipherService.saveCollectionsWithServer(encryptedCipher); + savedCipher = await this.cipherService.saveCollectionsWithServer( + encryptedCipher, + activeUserId, + ); } } diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts index e7deb78c868..9b12139b00e 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts @@ -2,8 +2,11 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; @@ -38,10 +41,12 @@ export class AutofillOptionsViewComponent { constructor( private platformUtilsService: PlatformUtilsService, private cipherService: CipherService, + private accountService: AccountService, ) {} async openWebsite(selectedUri: string) { - await this.cipherService.updateLastLaunchedDate(this.cipherId); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.cipherService.updateLastLaunchedDate(this.cipherId, activeUserId); this.platformUtilsService.launchUri(selectedUri); } } 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 b4a0d4841f8..8503604bf7c 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -116,7 +116,7 @@ {{ "verificationCodeTotp" | i18n }}
diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 41a4828f645..c95b2040fd2 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -2,15 +2,7 @@ // @ts-strict-ignore import { CommonModule, DatePipe } from "@angular/common"; import { Component, inject, Input } from "@angular/core"; -import { - BehaviorSubject, - combineLatest, - filter, - map, - Observable, - shareReplay, - switchMap, -} from "rxjs"; +import { Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -21,13 +13,13 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { - BadgeModule, - ColorPasswordModule, FormFieldModule, - IconButtonModule, SectionComponent, SectionHeaderComponent, TypographyModule, + IconButtonModule, + BadgeModule, + ColorPasswordModule, } from "@bitwarden/components"; import { BitTotpCountdownComponent } from "../../components/totp-countdown/totp-countdown.component"; @@ -57,31 +49,13 @@ type TotpCodeValues = { ], }) export class LoginCredentialsViewComponent { - @Input() - get cipher(): CipherView { - return this._cipher$.value; - } - set cipher(value: CipherView) { - this._cipher$.next(value); - } - private _cipher$ = new BehaviorSubject(null); + @Input() cipher: CipherView; - private _userHasPremium$: Observable = this.accountService.activeAccount$.pipe( + isPremium$: Observable = this.accountService.activeAccount$.pipe( switchMap((account) => this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ), ); - - allowTotpGeneration$: Observable = combineLatest([ - this._userHasPremium$, - this._cipher$.pipe(filter((c) => c != null)), - ]).pipe( - map(([userHasPremium, cipher]) => { - // User premium status only applies to personal ciphers, organizationUseTotp applies to organization ciphers - return (userHasPremium && cipher.organizationId == null) || cipher.organizationUseTotp; - }), - shareReplay({ refCount: true, bufferSize: 1 }), - ); showPasswordCount: boolean = false; passwordRevealed: boolean = false; totpCodeCopyObj: TotpCodeValues; diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index a8ef3557385..76a6a1b10a6 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -179,7 +179,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private get selectedOrgId(): OrganizationId { return this.formGroup.getRawValue().selectedOrg || this.params.organizationId; } - private activeUserId: UserId; private destroy$ = new Subject(); constructor( @@ -193,10 +192,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI ) {} async ngOnInit() { - 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) { @@ -253,12 +248,15 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI .filter((i) => i.organizationId) .map((i) => i.id as CipherId); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + // Move personal items to the organization if (this.personalItemsCount > 0) { await this.moveToOrganization( this.selectedOrgId, this.params.ciphers.filter((c) => c.organizationId == null), this.formGroup.controls.collections.value.map((i) => i.id as CollectionId), + activeUserId, ); } @@ -267,8 +265,8 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI // Update assigned collections for single org cipher or bulk update collections for multiple org ciphers await (isSingleOrgCipher - ? this.updateAssignedCollections(this.editableItems[0]) - : this.bulkUpdateCollections(cipherIds)); + ? this.updateAssignedCollections(this.editableItems[0], activeUserId) + : this.bulkUpdateCollections(cipherIds, activeUserId)); this.toastService.showToast({ variant: "success", @@ -447,12 +445,13 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI organizationId: OrganizationId, shareableCiphers: CipherView[], selectedCollectionIds: CollectionId[], + userId: UserId, ) { await this.cipherService.shareManyWithServer( shareableCiphers, organizationId, selectedCollectionIds, - this.activeUserId, + userId, ); this.toastService.showToast({ @@ -465,10 +464,11 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI }); } - private async bulkUpdateCollections(cipherIds: CipherId[]) { + private async bulkUpdateCollections(cipherIds: CipherId[], userId: UserId) { if (this.formGroup.controls.collections.value.length > 0) { await this.cipherService.bulkUpdateCollectionsWithServer( this.selectedOrgId, + userId, cipherIds, this.formGroup.controls.collections.value.map((i) => i.id as CollectionId), false, @@ -483,6 +483,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI ) { await this.cipherService.bulkUpdateCollectionsWithServer( this.selectedOrgId, + userId, cipherIds, [this.params.activeCollection.id as CollectionId], true, @@ -490,14 +491,14 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI } } - private async updateAssignedCollections(cipherView: CipherView) { + private async updateAssignedCollections(cipherView: CipherView, userId: UserId) { const { collections } = this.formGroup.getRawValue(); cipherView.collectionIds = collections.map((i) => i.id as CollectionId); - const cipher = await this.cipherService.encrypt(cipherView, this.activeUserId); + const cipher = await this.cipherService.encrypt(cipherView, userId); if (this.params.isSingleCipherAdmin) { await this.cipherService.saveCollectionsWithServerAdmin(cipher); } else { - await this.cipherService.saveCollectionsWithServer(cipher); + await this.cipherService.saveCollectionsWithServer(cipher, userId); } } } diff --git a/package-lock.json b/package-lock.json index 3883aec1687..20ada5f5931 100644 --- a/package-lock.json +++ b/package-lock.json @@ -190,7 +190,7 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.2.0" + "version": "2025.2.1" }, "apps/cli": { "name": "@bitwarden/cli", @@ -244,7 +244,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.2.0" + "version": "2025.2.1" }, "libs/admin-console": { "name": "@bitwarden/admin-console",