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 @@
-
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
}}
+
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 @@
-