mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 13:40:06 +00:00
Merge branch 'main' into reduce-desktop-disk-writes
This commit is contained in:
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -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
|
||||
|
||||
33
.github/workflows/build-browser-target.yml
vendored
Normal file
33
.github/workflows/build-browser-target.yml
vendored
Normal file
@@ -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
|
||||
|
||||
24
.github/workflows/build-browser.yml
vendored
24
.github/workflows/build-browser.yml
vendored
@@ -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 }}
|
||||
|
||||
33
.github/workflows/build-cli-target.yml
vendored
Normal file
33
.github/workflows/build-cli-target.yml
vendored
Normal file
@@ -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
|
||||
|
||||
27
.github/workflows/build-cli.yml
vendored
27
.github/workflows/build-cli.yml
vendored
@@ -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 ../
|
||||
|
||||
32
.github/workflows/build-desktop-target.yml
vendored
Normal file
32
.github/workflows/build-desktop-target.yml
vendored
Normal file
@@ -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
|
||||
|
||||
68
.github/workflows/build-desktop.yml
vendored
68
.github/workflows/build-desktop.yml
vendored
@@ -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
|
||||
|
||||
32
.github/workflows/build-web-target.yml
vendored
Normal file
32
.github/workflows/build-web-target.yml
vendored
Normal file
@@ -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
|
||||
|
||||
30
.github/workflows/build-web.yml
vendored
30
.github/workflows/build-web.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/lint.yml
vendored
10
.github/workflows/lint.yml
vendored
@@ -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: {}
|
||||
|
||||
|
||||
1
.github/workflows/scan.yml
vendored
1
.github/workflows/scan.yml
vendored
@@ -77,3 +77,4 @@ jobs:
|
||||
-Dsonar.sources=.
|
||||
-Dsonar.test.inclusions=**/*.spec.ts
|
||||
-Dsonar.exclusions=**/*.spec.ts
|
||||
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" [formGroup]="formGroup">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/home">{{ "cancel" | i18n }}</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "createAccount" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<button type="submit" [disabled]="form.loading">
|
||||
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
|
||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input id="email" type="email" formControlName="email" appInputVerbatim="false" />
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<div class="box-content-row-flex">
|
||||
<div class="row-main">
|
||||
<label for="masterPassword">
|
||||
{{ "masterPass" | i18n }}
|
||||
<strong class="sub-label text-{{ color }}" *ngIf="text">
|
||||
{{ text }}
|
||||
</strong>
|
||||
</label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
aria-describedby="masterPasswordHelp"
|
||||
class="monospaced"
|
||||
formControlName="masterPassword"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
[attr.aria-pressed]="showPassword"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-password-strength
|
||||
[password]="formGroup.get('masterPassword')?.value"
|
||||
[email]="formGroup.get('email')?.value"
|
||||
[name]="formGroup.get('name')?.value"
|
||||
(passwordStrengthResult)="getStrengthResult($event)"
|
||||
(passwordScoreColor)="getPasswordScoreText($event)"
|
||||
>
|
||||
</app-password-strength>
|
||||
</div>
|
||||
</div>
|
||||
<div id="masterPasswordHelp" class="box-footer">
|
||||
<b>{{ "important" | i18n }}</b> {{ "masterPasswordHint" | i18n }}
|
||||
{{ characterMinimumMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
class="monospaced"
|
||||
formControlName="confirmMasterPassword"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="togglePassword()"
|
||||
[attr.aria-pressed]="showPassword"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||
<input id="hint" type="text" aria-describedby="hintHelp" formControlName="hint" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="hintHelp" class="box-footer">
|
||||
{{ "masterPassHintDesc" | i18n }}
|
||||
</div>
|
||||
<div class="box-content row-top-padding">
|
||||
<div
|
||||
class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break"
|
||||
appBoxRow
|
||||
>
|
||||
<input type="checkbox" id="checkForBreaches" formControlName="checkForBreaches" />
|
||||
<label for="checkForBreaches">
|
||||
{{ "checkForBreaches" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
</div>
|
||||
<div class="box last" *ngIf="showTerms">
|
||||
<div class="box-content">
|
||||
<div
|
||||
class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break"
|
||||
appBoxRow
|
||||
>
|
||||
<input type="checkbox" id="acceptPolicies" formControlName="acceptPolicies" />
|
||||
<label for="acceptPolicies">
|
||||
{{ "acceptPolicies" | i18n }}<br />
|
||||
<a href="https://bitwarden.com/terms/" target="_blank" rel="noreferrer">{{
|
||||
"termsOfService" | i18n
|
||||
}}</a
|
||||
>,
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noreferrer">{{
|
||||
"privacyPolicy" | i18n
|
||||
}}</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</form>
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -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<string>,
|
||||
private addPasswordCallback: (password: string) => Promise<void>,
|
||||
) {
|
||||
@@ -405,13 +409,20 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
currentTab: chrome.tabs.Tab,
|
||||
updateAllCipherTypes: boolean,
|
||||
): Promise<CipherView[]> {
|
||||
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<CipherView[]> {
|
||||
private async getAllCipherTypeViews(
|
||||
currentTab: chrome.tabs.Tab,
|
||||
userId: UserId,
|
||||
): Promise<CipherView[]> {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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<AuthService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -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<Promise<void>, [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,
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<string | null> {
|
||||
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)) {
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> {
|
||||
|
||||
@@ -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<void> {
|
||||
@@ -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<void>((resolve) =>
|
||||
(this.sidebarAction as OperaSidebarAction).setIcon(options, () => resolve()),
|
||||
|
||||
@@ -100,9 +100,7 @@ Usage example:
|
||||
|
||||
### Transparent header
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.TransparentHeader} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.TransparentHeader} />
|
||||
|
||||
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
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.Notice} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.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.
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.PopupTabNavigation} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.PopupTabNavigation} />
|
||||
|
||||
## Extension Page
|
||||
|
||||
Examples of using just the `popup-page` component, without and with a footer.
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.PopupPage} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.PopupPage} />
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.PopupPageWithFooter} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.PopupPageWithFooter} />
|
||||
|
||||
## Popped out
|
||||
|
||||
When the browser extension is popped out, the "popout" button should not be passed to the header.
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.PoppedOut} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.PoppedOut} />
|
||||
|
||||
# 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.
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.CenteredContent} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.CenteredContent} />
|
||||
|
||||
## Loading
|
||||
|
||||
An example of what the loading state looks like.
|
||||
|
||||
<Canvas>
|
||||
<Story of={stories.Loading} />
|
||||
</Canvas>
|
||||
<Canvas of={stories.Loading} />
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<AddEditCipherInfo | null>(null);
|
||||
cipherServiceMock = mock<CipherService>();
|
||||
cipherServiceMock.addEditCipherInfo$ =
|
||||
addEditCipherInfo$.asObservable() as Observable<AddEditCipherInfo>;
|
||||
cipherServiceMock = mock<CipherService>({
|
||||
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, {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CipherView> = 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<CipherView> = 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);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -59,7 +59,9 @@ describe("VaultHeaderV2Component", () => {
|
||||
providers: [
|
||||
{
|
||||
provide: CipherService,
|
||||
useValue: mock<CipherService>({ cipherViews$: new BehaviorSubject([]) }),
|
||||
useValue: mock<CipherService>({
|
||||
cipherViews$: jest.fn().mockReturnValue(new BehaviorSubject([])),
|
||||
}),
|
||||
},
|
||||
{ provide: VaultSettingsService, useValue: mock<VaultSettingsService>() },
|
||||
{ provide: FolderService, useValue: mock<FolderService>() },
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<PasswordHistoryV2Component>;
|
||||
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<CipherService>({ get: getCipher }) },
|
||||
{
|
||||
provide: AccountService,
|
||||
useValue: mock<AccountService>({
|
||||
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", () => {
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
|
||||
|
||||
@@ -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<Organization>;
|
||||
@@ -117,14 +121,20 @@ export class ViewV2Component {
|
||||
subscribeToParams(): void {
|
||||
this.route.queryParams
|
||||
.pipe(
|
||||
switchMap(async (params): Promise<CipherView> => {
|
||||
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<void> => {
|
||||
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 {
|
||||
|
||||
@@ -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<Date>;
|
||||
|
||||
let ciphersSubject: BehaviorSubject<Record<CipherId, CipherData>>;
|
||||
let localDataSubject: BehaviorSubject<Record<CipherId, LocalData>>;
|
||||
let failedToDecryptCiphersSubject: BehaviorSubject<CipherView[]>;
|
||||
|
||||
const cipherServiceMock = mock<CipherService>();
|
||||
const vaultSettingsServiceMock = mock<VaultSettingsService>();
|
||||
const organizationServiceMock = mock<OrganizationService>();
|
||||
@@ -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<Record<CipherId, CipherData>>({});
|
||||
localDataSubject = new BehaviorSubject<Record<CipherId, LocalData>>({});
|
||||
failedToDecryptCiphersSubject = new BehaviorSubject<CipherView[]>([]);
|
||||
|
||||
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<any>).next(null);
|
||||
ciphersSubject.next({});
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
@@ -169,7 +188,7 @@ describe("VaultPopupItemsService", () => {
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
(cipherServiceMock.localData$ as BehaviorSubject<any>).next(null);
|
||||
localDataSubject.next({});
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
@@ -373,7 +392,7 @@ describe("VaultPopupItemsService", () => {
|
||||
|
||||
cipherServiceMock.getAllDecrypted.mockResolvedValue(ciphers);
|
||||
|
||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).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<any>).next(null);
|
||||
ciphersSubject.next({});
|
||||
|
||||
await trackedCiphers.pauseUntilReceived(2);
|
||||
|
||||
|
||||
@@ -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<CipherView[]> = 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<CipherView[]> = 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) {
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("VaultPopupListFiltersService", () => {
|
||||
} as unknown as FolderService;
|
||||
|
||||
const cipherService = {
|
||||
cipherViews$,
|
||||
cipherViews$: () => cipherViews$,
|
||||
} as unknown as CipherService;
|
||||
|
||||
const organizationService = {
|
||||
|
||||
@@ -93,16 +93,6 @@ export class VaultPopupListFiltersService {
|
||||
*/
|
||||
private cipherViews: CipherView[] = [];
|
||||
|
||||
/**
|
||||
* Observable of cipher views
|
||||
*/
|
||||
private cipherViews$: Observable<CipherView[]> = 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<ChipSelectOption<FolderView>[]> = 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")),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<CipherView | CipherView[]> {
|
||||
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)) {
|
||||
|
||||
@@ -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() !== "") {
|
||||
|
||||
@@ -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<Response> {
|
||||
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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -436,6 +436,23 @@
|
||||
"enableSshAgentDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="!isLinux">
|
||||
<div class="checkbox">
|
||||
<label for="allowScreenshots">
|
||||
<input
|
||||
id="allowScreenshots"
|
||||
type="checkbox"
|
||||
aria-describedby="allowScreenshotsHelp"
|
||||
formControlName="allowScreenshots"
|
||||
(change)="savePreventScreenshots()"
|
||||
/>
|
||||
{{ "allowScreenshots" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
<small id="allowScreenshotsHelp" class="help-block">{{
|
||||
"allowScreenshotsDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showDuckDuckGoIntegrationOption">
|
||||
<div class="checkbox">
|
||||
<label for="enableDuckDuckGoBrowserIntegration">
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, Observable, Subject, firstValueFrom } from "rxjs";
|
||||
import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators";
|
||||
import {
|
||||
concatMap,
|
||||
debounceTime,
|
||||
filter,
|
||||
map,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
tap,
|
||||
timeout,
|
||||
} from "rxjs/operators";
|
||||
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
@@ -116,6 +125,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
}),
|
||||
enableHardwareAcceleration: true,
|
||||
enableSshAgent: false,
|
||||
allowScreenshots: false,
|
||||
enableDuckDuckGoBrowserIntegration: false,
|
||||
theme: [null as ThemeType | null],
|
||||
locale: [null as string | null],
|
||||
@@ -287,6 +297,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.desktopSettingsService.hardwareAcceleration$,
|
||||
),
|
||||
enableSshAgent: await firstValueFrom(this.desktopSettingsService.sshAgentEnabled$),
|
||||
allowScreenshots: !(await firstValueFrom(this.desktopSettingsService.preventScreenshots$)),
|
||||
theme: await firstValueFrom(this.themeStateService.selectedTheme$),
|
||||
locale: await firstValueFrom(this.i18nService.userSetLocale$),
|
||||
};
|
||||
@@ -769,6 +780,33 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
await this.desktopSettingsService.setSshAgentEnabled(this.form.value.enableSshAgent);
|
||||
}
|
||||
|
||||
async savePreventScreenshots() {
|
||||
await this.desktopSettingsService.setPreventScreenshots(!this.form.value.allowScreenshots);
|
||||
|
||||
if (!this.form.value.allowScreenshots) {
|
||||
const dialogRef = this.dialogService.openSimpleDialogRef({
|
||||
title: { key: "confirmWindowStillVisibleTitle" },
|
||||
content: { key: "confirmWindowStillVisibleContent" },
|
||||
acceptButtonText: { key: "ok" },
|
||||
cancelButtonText: null,
|
||||
type: "info",
|
||||
});
|
||||
let enabled = true;
|
||||
try {
|
||||
enabled = await firstValueFrom(dialogRef.closed.pipe(timeout(10000)));
|
||||
} catch {
|
||||
enabled = false;
|
||||
} finally {
|
||||
dialogRef.close();
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
await this.desktopSettingsService.setPreventScreenshots(false);
|
||||
this.form.controls.allowScreenshots.setValue(true, { emitEvent: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async generateVaultTimeoutOptions(): Promise<VaultTimeoutOption[]> {
|
||||
let vaultTimeoutOptions: VaultTimeoutOption[] = [
|
||||
{ name: this.i18nService.t("oneMinute"), value: 1 },
|
||||
|
||||
@@ -56,7 +56,6 @@ import { HintComponent } from "../auth/hint.component";
|
||||
import { LoginDecryptionOptionsComponentV1 } from "../auth/login/login-decryption-options/login-decryption-options-v1.component";
|
||||
import { LoginComponentV1 } from "../auth/login/login-v1.component";
|
||||
import { LoginViaAuthRequestComponentV1 } from "../auth/login/login-via-auth-request-v1.component";
|
||||
import { RegisterComponent } from "../auth/register.component";
|
||||
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||
import { SsoComponentV1 } from "../auth/sso-v1.component";
|
||||
@@ -136,7 +135,6 @@ const routes: Routes = [
|
||||
},
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
{ path: "register", component: RegisterComponent },
|
||||
{
|
||||
path: "new-device-notice",
|
||||
component: AnonLayoutWrapperComponent,
|
||||
|
||||
@@ -318,20 +318,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "upgradeOrganization": {
|
||||
const upgradeConfirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "upgradeOrganization" },
|
||||
content: { key: "upgradeOrganizationDesc" },
|
||||
acceptButtonText: { key: "learnMore" },
|
||||
type: "info",
|
||||
});
|
||||
if (upgradeConfirmed) {
|
||||
this.platformUtilsService.launchUri(
|
||||
"https://bitwarden.com/help/upgrade-from-individual-to-org/",
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "emailVerificationRequired": {
|
||||
const emailVerificationConfirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "emailVerificationRequired" },
|
||||
|
||||
@@ -15,7 +15,6 @@ import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||
import { EnvironmentComponent } from "../auth/environment.component";
|
||||
import { HintComponent } from "../auth/hint.component";
|
||||
import { LoginModule } from "../auth/login/login.module";
|
||||
import { RegisterComponent } from "../auth/register.component";
|
||||
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||
import { SsoComponentV1 } from "../auth/sso-v1.component";
|
||||
@@ -80,7 +79,6 @@ import { SendComponent } from "./tools/send/send.component";
|
||||
NavComponent,
|
||||
PasswordHistoryComponent,
|
||||
PremiumComponent,
|
||||
RegisterComponent,
|
||||
RemovePasswordComponent,
|
||||
SearchComponent,
|
||||
SendAddEditComponent,
|
||||
|
||||
@@ -76,12 +76,10 @@
|
||||
></app-avatar>
|
||||
<div class="accountInfo">
|
||||
<span class="sr-only">{{ "switchAccount" | i18n }}: </span>
|
||||
<span class="email" aria-hidden="true">{{ account.value.email }}</span>
|
||||
<span class="server" aria-hidden="true">
|
||||
<span class="sr-only"> / </span>{{ account.value.server }}
|
||||
</span>
|
||||
<span class="status" aria-hidden="true"
|
||||
><span class="sr-only"> (</span
|
||||
<span class="email">{{ account.value.email }}</span>
|
||||
<span class="server"> <span class="sr-only"> / </span>{{ account.value.server }} </span>
|
||||
<span class="status">
|
||||
<span class="sr-only"> (</span
|
||||
>{{
|
||||
(account.value.authenticationStatus === authStatus.Unlocked ? "unlocked" : "locked")
|
||||
| i18n
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
<form
|
||||
id="register-page"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
[formGroup]="formGroup"
|
||||
>
|
||||
<div class="content">
|
||||
<h1>{{ "createAccount" | i18n }}</h1>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="email">{{ "emailAddress" | i18n }}</label>
|
||||
<input id="email" type="email" formControlName="email" appInputVerbatim />
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<div class="box-content-row-flex">
|
||||
<div class="row-main">
|
||||
<label for="masterPassword">
|
||||
{{ "masterPass" | i18n }}
|
||||
<strong class="sub-label text-{{ color }}" *ngIf="text">
|
||||
{{ text }}
|
||||
</strong>
|
||||
</label>
|
||||
<input
|
||||
id="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
class="monospaced"
|
||||
aria-describedby="masterPasswordHelp"
|
||||
formControlName="masterPassword"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
[attr.aria-pressed]="showPassword"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-password-strength
|
||||
[password]="formGroup.get('masterPassword')?.value"
|
||||
[email]="formGroup.get('email')?.value"
|
||||
[name]="formGroup.get('name')?.value"
|
||||
(passwordStrengthResult)="getStrengthResult($event)"
|
||||
(passwordScoreColor)="getPasswordScoreText($event)"
|
||||
>
|
||||
</app-password-strength>
|
||||
</div>
|
||||
</div>
|
||||
<div id="masterPasswordHelp" class="box-footer">
|
||||
<b>{{ "important" | i18n }}</b> {{ "masterPasswordHint" | i18n }}
|
||||
{{ characterMinimumMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
|
||||
<input
|
||||
id="masterPasswordRetype"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
class="monospaced"
|
||||
formControlName="confirmMasterPassword"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
[attr.aria-pressed]="showPassword"
|
||||
(click)="togglePassword()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||
<input id="hint" type="text" aria-describedby="hintHelp" formControlName="hint" />
|
||||
</div>
|
||||
<div class="box last" [hidden]="!showCaptcha()">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row">
|
||||
<iframe
|
||||
id="hcaptcha_iframe"
|
||||
height="80"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
></iframe>
|
||||
<button class="btn block" type="button" routerLink="/accessibility-cookie">
|
||||
<i class="bwi bwi-universal-access" aria-hidden="true"></i>
|
||||
{{ "loadAccessibilityCookie" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hintHelp" class="box-footer">
|
||||
{{ "masterPassHintDesc" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box last">
|
||||
<div class="box-footer checkbox">
|
||||
<input type="checkbox" id="checkForBreaches" formControlName="checkForBreaches" />
|
||||
<label for="checkForBreaches">
|
||||
{{ "checkForBreaches" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="box-footer checkbox" *ngIf="showTerms">
|
||||
<input type="checkbox" id="acceptPolicies" formControlName="acceptPolicies" />
|
||||
<label for="acceptPolicies">
|
||||
{{ "acceptPolicies" | i18n }}<br />
|
||||
<a href="https://bitwarden.com/terms/" target="_blank" rel="noreferrer">{{
|
||||
"termsOfService" | i18n
|
||||
}}</a
|
||||
>,
|
||||
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noreferrer">{{
|
||||
"privacyPolicy" | i18n
|
||||
}}</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="submit" class="btn primary block" [disabled]="form.loading">
|
||||
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
|
||||
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" routerLink="/login" class="btn block">{{ "cancel" | i18n }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,83 +0,0 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit } 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 { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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";
|
||||
|
||||
const BroadcasterSubscriptionId = "RegisterComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-register",
|
||||
templateUrl: "register.component.html",
|
||||
})
|
||||
export class RegisterComponent extends BaseRegisterComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
formBuilder: UntypedFormBuilder,
|
||||
loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
keyService: KeyService,
|
||||
apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
logService: LogService,
|
||||
auditService: AuditService,
|
||||
dialogService: DialogService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
formValidationErrorService,
|
||||
formBuilder,
|
||||
loginStrategyService,
|
||||
router,
|
||||
i18nService,
|
||||
keyService,
|
||||
apiService,
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
logService,
|
||||
auditService,
|
||||
dialogService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
this.ngZone.run(() => {
|
||||
switch (message.command) {
|
||||
case "windowHidden":
|
||||
this.onWindowHidden();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
onWindowHidden() {
|
||||
this.showPassword = false;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
EMPTY,
|
||||
Subject,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
mergeMap,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -27,6 +29,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils";
|
||||
import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils";
|
||||
import { guidToRawFormat } from "@bitwarden/common/platform/services/fido2/guid-utils";
|
||||
import { 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";
|
||||
@@ -60,7 +63,11 @@ export class DesktopAutofillService implements OnDestroy {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
return this.cipherService.cipherViews$;
|
||||
return this.accountService.activeAccount$.pipe(
|
||||
map((account) => account?.id),
|
||||
filter((userId): userId is UserId => userId != null),
|
||||
switchMap((userId) => this.cipherService.cipherViews$(userId)),
|
||||
);
|
||||
}),
|
||||
// TODO: This will unset all the autofill credentials on the OS
|
||||
// when the account locks. We should instead explicilty clear the credentials
|
||||
@@ -164,17 +171,22 @@ export class DesktopAutofillService implements OnDestroy {
|
||||
// TODO: For some reason the credentialId is passed as an empty array in the request, so we need to
|
||||
// get it from the cipher. For that we use the recordIdentifier, which is the cipherId.
|
||||
if (request.recordIdentifier && request.credentialId.length === 0) {
|
||||
const cipher = await this.cipherService.get(request.recordIdentifier);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
if (!activeUserId) {
|
||||
this.logService.error("listenPasskeyAssertion error", "Active user not found");
|
||||
callback(new Error("Active user not found"), null);
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId);
|
||||
if (!cipher) {
|
||||
this.logService.error("listenPasskeyAssertion error", "Cipher not found");
|
||||
callback(new Error("Cipher not found"), null);
|
||||
return;
|
||||
}
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
|
||||
const decrypted = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
|
||||
@@ -3496,14 +3496,17 @@
|
||||
"changeAcctEmail": {
|
||||
"message": "Change account email"
|
||||
},
|
||||
"organizationUpgradeRequired": {
|
||||
"message": "Organization upgrade required"
|
||||
"allowScreenshots": {
|
||||
"message": "Allow screen capture"
|
||||
},
|
||||
"upgradeOrganization": {
|
||||
"message": "Upgrade organization"
|
||||
"allowScreenshotsDesc": {
|
||||
"message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays."
|
||||
},
|
||||
"upgradeOrganizationDesc": {
|
||||
"message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features."
|
||||
"confirmWindowStillVisibleTitle": {
|
||||
"message": "Confirm window still visible"
|
||||
},
|
||||
"confirmWindowStillVisibleContent": {
|
||||
"message": "Please confirm that the window is still visible."
|
||||
},
|
||||
"updateBrowserOrDisableFingerprintDialogTitle": {
|
||||
"message": "Extension update required"
|
||||
|
||||
@@ -76,6 +76,13 @@ export class WindowMain {
|
||||
}
|
||||
});
|
||||
|
||||
this.desktopSettingsService.preventScreenshots$.subscribe((prevent) => {
|
||||
if (this.win == null) {
|
||||
return;
|
||||
}
|
||||
this.win.setContentProtection(prevent);
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
if (!isMacAppStore()) {
|
||||
@@ -277,6 +284,14 @@ export class WindowMain {
|
||||
});
|
||||
});
|
||||
|
||||
firstValueFrom(this.desktopSettingsService.preventScreenshots$)
|
||||
.then((preventScreenshots) => {
|
||||
this.win.setContentProtection(preventScreenshots);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.logService.error(e);
|
||||
});
|
||||
|
||||
if (this.createWindowCallback) {
|
||||
this.createWindowCallback(this.win);
|
||||
}
|
||||
|
||||
4
apps/desktop/src/package-lock.json
generated
4
apps/desktop/src/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@bitwarden/desktop",
|
||||
"version": "2025.2.0",
|
||||
"version": "2025.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@bitwarden/desktop",
|
||||
"version": "2025.2.0",
|
||||
"version": "2025.2.1",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@bitwarden/desktop-napi": "file:../desktop_native/napi"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@bitwarden/desktop",
|
||||
"productName": "Bitwarden",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2025.2.0",
|
||||
"version": "2025.2.1",
|
||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||
"homepage": "https://bitwarden.com",
|
||||
"license": "GPL-3.0",
|
||||
|
||||
@@ -75,6 +75,14 @@ const MINIMIZE_ON_COPY = new UserKeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "
|
||||
clearOn: [], // User setting, no need to clear
|
||||
});
|
||||
|
||||
const PREVENT_SCREENSHOTS = new KeyDefinition<boolean>(
|
||||
DESKTOP_SETTINGS_DISK,
|
||||
"preventScreenshots",
|
||||
{
|
||||
deserializer: (b) => b,
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Various settings for controlling application behavior specific to the desktop client.
|
||||
*/
|
||||
@@ -147,6 +155,13 @@ export class DesktopSettingsService {
|
||||
|
||||
sshAgentEnabled$ = this.sshAgentEnabledState.state$.pipe(map(Boolean));
|
||||
|
||||
private readonly preventScreenshotState = this.stateProvider.getGlobal(PREVENT_SCREENSHOTS);
|
||||
|
||||
/**
|
||||
* The application setting for whether or not to allow screenshots of the app.
|
||||
*/
|
||||
preventScreenshots$ = this.preventScreenshotState.state$.pipe(map(Boolean));
|
||||
|
||||
private readonly minimizeOnCopyState = this.stateProvider.getActive(MINIMIZE_ON_COPY);
|
||||
|
||||
/**
|
||||
@@ -270,4 +285,12 @@ export class DesktopSettingsService {
|
||||
async setMinimizeOnCopy(value: boolean, userId: UserId) {
|
||||
await this.stateProvider.getUser(userId, MINIMIZE_ON_COPY).update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setting for whether or not the screenshot protection is enabled.
|
||||
* @param value `true` if the screenshot protection is enabled, `false` if it is not.
|
||||
*/
|
||||
async setPreventScreenshots(value: boolean) {
|
||||
await this.preventScreenshotState.update(() => value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
@@ -92,14 +93,14 @@ export class SshAgentService implements OnDestroy {
|
||||
}),
|
||||
filter(({ enabled }) => enabled),
|
||||
map(({ message }) => message),
|
||||
withLatestFrom(this.authService.activeAccountStatus$),
|
||||
withLatestFrom(this.authService.activeAccountStatus$, this.accountService.activeAccount$),
|
||||
// This switchMap handles unlocking the vault if it is locked:
|
||||
// - If the vault is locked, we will wait for it to be unlocked.
|
||||
// - If the vault is not unlocked within the timeout, we will abort the flow.
|
||||
// - If the vault is unlocked, we will continue with the flow.
|
||||
// switchMap is used here to prevent multiple requests from being processed at the same time,
|
||||
// and will cancel the previous request if a new one is received.
|
||||
switchMap(([message, status]) => {
|
||||
switchMap(([message, status, account]) => {
|
||||
if (status !== AuthenticationStatus.Unlocked) {
|
||||
ipc.platform.focusWindow();
|
||||
this.toastService.showToast({
|
||||
@@ -133,11 +134,11 @@ export class SshAgentService implements OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
return of(message);
|
||||
return of([message, account.id]);
|
||||
}),
|
||||
// This switchMap handles fetching the ciphers from the vault.
|
||||
switchMap((message) =>
|
||||
from(this.cipherService.getAllDecrypted()).pipe(
|
||||
switchMap(([message, userId]: [Record<string, unknown>, UserId]) =>
|
||||
from(this.cipherService.getAllDecrypted(userId)).pipe(
|
||||
map((ciphers) => [message, ciphers] as const),
|
||||
),
|
||||
),
|
||||
@@ -245,7 +246,7 @@ export class SshAgentService implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecrypted();
|
||||
const ciphers = await this.cipherService.getAllDecrypted(activeAccount.id);
|
||||
if (ciphers == null) {
|
||||
await ipc.platform.sshAgent.lock();
|
||||
return;
|
||||
|
||||
@@ -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 { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -65,9 +66,7 @@ export class EncryptedMessageHandlerService {
|
||||
}
|
||||
|
||||
private async checkUserStatus(userId: string): Promise<string> {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
if (userId !== activeUserId) {
|
||||
return "not-active-user";
|
||||
@@ -83,9 +82,7 @@ export class EncryptedMessageHandlerService {
|
||||
|
||||
private async statusCommandHandler(): Promise<AccountStatusResponse[]> {
|
||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
if (!accounts || !Object.keys(accounts)) {
|
||||
return [];
|
||||
@@ -114,16 +111,14 @@ export class EncryptedMessageHandlerService {
|
||||
}
|
||||
|
||||
const ciphersResponse: CipherResponse[] = [];
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
const authStatus = await this.authService.getAuthStatus(activeUserId);
|
||||
|
||||
if (authStatus !== AuthenticationStatus.Unlocked) {
|
||||
return { error: "locked" };
|
||||
}
|
||||
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri);
|
||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri, activeUserId);
|
||||
ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
||||
|
||||
ciphers.forEach((c) => {
|
||||
@@ -166,9 +161,7 @@ export class EncryptedMessageHandlerService {
|
||||
cipherView.login.uris[0].uri = credentialCreatePayload.uri;
|
||||
|
||||
try {
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
const encrypted = await this.cipherService.encrypt(cipherView, activeUserId);
|
||||
await this.cipherService.createWithServer(encrypted);
|
||||
|
||||
@@ -200,13 +193,16 @@ export class EncryptedMessageHandlerService {
|
||||
}
|
||||
|
||||
try {
|
||||
const cipher = await this.cipherService.get(credentialUpdatePayload.credentialId);
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
const cipher = await this.cipherService.get(
|
||||
credentialUpdatePayload.credentialId,
|
||||
activeUserId,
|
||||
);
|
||||
if (cipher === null) {
|
||||
return { status: "failure" };
|
||||
}
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||
);
|
||||
|
||||
const cipherView = await cipher.decrypt(
|
||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { distinctUntilChanged } from "rxjs";
|
||||
|
||||
import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
@@ -20,8 +21,9 @@ export class VaultItemsComponent extends BaseVaultItemsComponent {
|
||||
searchService: SearchService,
|
||||
searchBarService: SearchBarService,
|
||||
cipherService: CipherService,
|
||||
accountService: AccountService,
|
||||
) {
|
||||
super(searchService, cipherService);
|
||||
super(searchService, cipherService, accountService);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
searchBarService.searchText$.pipe(distinctUntilChanged()).subscribe((searchText) => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { combineLatest, firstValueFrom, Subject, takeUntil, switchMap } from "rxjs";
|
||||
import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs";
|
||||
import { filter, first, map, take } from "rxjs/operators";
|
||||
|
||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
@@ -19,6 +19,7 @@ import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
@@ -236,15 +237,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
// Store a reference to the current active account during page init
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
// Combine with the activeAccount$ to ensure we only show the dialog for the current account from ngOnInit.
|
||||
// The account switching process updates the cipherService before Vault is destroyed and would cause duplicate emissions
|
||||
combineLatest([this.accountService.activeAccount$, this.cipherService.failedToDecryptCiphers$])
|
||||
this.cipherService
|
||||
.failedToDecryptCiphers$(activeUserId)
|
||||
.pipe(
|
||||
filter(([account]) => account.id === activeAccount.id),
|
||||
map(([_, ciphers]) => ciphers.filter((c) => !c.isDeleted)),
|
||||
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
||||
filter((ciphers) => ciphers.length > 0),
|
||||
take(1),
|
||||
takeUntil(this.componentIsDestroyed$),
|
||||
|
||||
@@ -186,16 +186,6 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-flex totp" *ngIf="showUpgradeRequiredTotp">
|
||||
<div class="row-main">
|
||||
<span class="row-label">{{ "verificationCodeTotp" | i18n }}</span>
|
||||
<span class="row-label">
|
||||
<a [routerLink]="" (click)="upgradeOrganization()"
|
||||
>{{ "organizationUpgradeRequired" | i18n }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card -->
|
||||
<div *ngIf="cipher.card">
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<p bitTypography="body1" class="tw-mt-2" *ngIf="loaded">{{ "noTrustedContacts" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
@@ -263,7 +263,7 @@
|
||||
<p bitTypography="body1" *ngIf="loaded">{{ "noGrantedAccess" | i18n }}</p>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<bit-container>
|
||||
<div class="tabbed-header">
|
||||
<div class="tw-mt-6 tw-mb-2 tw-pb-2.5">
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
<h1>{{ "devices" | i18n }}</h1>
|
||||
<h1 class="tw-m-0">{{ "devices" | i18n }}</h1>
|
||||
<button
|
||||
[bitPopoverTriggerFor]="infoPopover"
|
||||
type="button"
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
{{ region.domain }}
|
||||
</a>
|
||||
</bit-menu>
|
||||
<div>
|
||||
<div bitTypography="body2">
|
||||
{{ "accessing" | i18n }}:
|
||||
<a [routerLink]="[]" [bitMenuTriggerFor]="environmentOptions">
|
||||
<b>{{ currentRegion?.domain }}</b
|
||||
><i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||
<b class="tw-text-primary-600 tw-font-semibold">{{ currentRegion?.domain }}</b>
|
||||
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { LayoutComponent, NavigationModule } from "@bitwarden/components";
|
||||
@@ -68,6 +69,12 @@ class MockAccountService implements Partial<AccountService> {
|
||||
});
|
||||
}
|
||||
|
||||
class MockPlatformUtilsService implements Partial<PlatformUtilsService> {
|
||||
isSelfHost() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "story-layout",
|
||||
template: `<ng-content></ng-content>`,
|
||||
@@ -105,6 +112,7 @@ export default {
|
||||
{ provide: AccountService, useClass: MockAccountService },
|
||||
{ provide: ProviderService, useClass: MockProviderService },
|
||||
{ provide: SyncService, useClass: MockSyncService },
|
||||
{ provide: PlatformUtilsService, useClass: MockPlatformUtilsService },
|
||||
ProductSwitcherService,
|
||||
{
|
||||
provide: I18nPipe,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { IconButtonModule, LinkModule, MenuModule } from "@bitwarden/components";
|
||||
@@ -68,6 +69,12 @@ class MockAccountService implements Partial<AccountService> {
|
||||
});
|
||||
}
|
||||
|
||||
class MockPlatformUtilsService implements Partial<PlatformUtilsService> {
|
||||
isSelfHost() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "story-layout",
|
||||
template: `<ng-content></ng-content>`,
|
||||
@@ -101,6 +108,8 @@ export default {
|
||||
{ provide: ProviderService, useClass: MockProviderService },
|
||||
MockProviderService,
|
||||
{ provide: SyncService, useClass: MockSyncService },
|
||||
{ provide: PlatformUtilsService, useClass: MockPlatformUtilsService },
|
||||
MockPlatformUtilsService,
|
||||
ProductSwitcherService,
|
||||
{
|
||||
provide: I18nService,
|
||||
|
||||
@@ -90,8 +90,7 @@ import { DomainRulesComponent } from "./settings/domain-rules.component";
|
||||
import { PreferencesComponent } from "./settings/preferences.component";
|
||||
import { CredentialGeneratorComponent } from "./tools/credential-generator/credential-generator.component";
|
||||
import { ReportsModule } from "./tools/reports";
|
||||
import { AccessComponent } from "./tools/send/access.component";
|
||||
import { SendAccessExplainerComponent } from "./tools/send/send-access-explainer.component";
|
||||
import { AccessComponent, SendAccessExplainerComponent } from "./tools/send/send-access";
|
||||
import { SendComponent } from "./tools/send/send.component";
|
||||
import { VaultModule } from "./vault/individual-vault/vault.module";
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AuthModule } from "./auth";
|
||||
import { LoginModule } from "./auth/login/login.module";
|
||||
import { TrialInitiationModule } from "./billing/trial-initiation/trial-initiation.module";
|
||||
import { LooseComponentsModule, SharedModule } from "./shared";
|
||||
import { AccessComponent } from "./tools/send/access.component";
|
||||
import { AccessComponent } from "./tools/send/send-access/access.component";
|
||||
import { OrganizationBadgeModule } from "./vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { VaultFilterModule } from "./vault/individual-vault/vault-filter/vault-filter.module";
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core";
|
||||
import { BehaviorSubject, Observable, Subject, switchMap, takeUntil } from "rxjs";
|
||||
import { BehaviorSubject, Observable, Subject, firstValueFrom, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -51,8 +52,10 @@ export class CipherReportComponent implements OnDestroy {
|
||||
private syncService: SyncService,
|
||||
) {
|
||||
this.organizations$ = this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) => this.organizationService.organizations$(account?.id)),
|
||||
getUserId,
|
||||
switchMap((userId) => this.organizationService.organizations$(userId)),
|
||||
);
|
||||
|
||||
this.organizations$.pipe(takeUntil(this.destroyed$)).subscribe((orgs) => {
|
||||
this.organizations = orgs;
|
||||
});
|
||||
@@ -182,7 +185,8 @@ export class CipherReportComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
protected async getAllCiphers(): Promise<CipherView[]> {
|
||||
return await this.cipherService.getAllDecrypted();
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
return await this.cipherService.getAllDecrypted(activeUserId);
|
||||
}
|
||||
|
||||
protected filterCiphersByOrg(ciphersList: CipherView[]) {
|
||||
|
||||
@@ -25,15 +25,14 @@ describe("ExposedPasswordsReportComponent", () => {
|
||||
let auditService: MockProxy<AuditService>;
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let syncServiceMock: MockProxy<SyncService>;
|
||||
let accountService: FakeAccountService;
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||
|
||||
beforeEach(() => {
|
||||
syncServiceMock = mock<SyncService>();
|
||||
auditService = mock<AuditService>();
|
||||
organizationService = mock<OrganizationService>();
|
||||
organizationService.organizations$.mockReturnValue(of([]));
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
@@ -24,14 +24,13 @@ describe("InactiveTwoFactorReportComponent", () => {
|
||||
let fixture: ComponentFixture<InactiveTwoFactorReportComponent>;
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let syncServiceMock: MockProxy<SyncService>;
|
||||
let accountService: FakeAccountService;
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||
|
||||
beforeEach(() => {
|
||||
organizationService = mock<OrganizationService>();
|
||||
organizationService.organizations$.mockReturnValue(of([]));
|
||||
syncServiceMock = mock<SyncService>();
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
@@ -23,15 +23,13 @@ describe("ReusedPasswordsReportComponent", () => {
|
||||
let fixture: ComponentFixture<ReusedPasswordsReportComponent>;
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let syncServiceMock: MockProxy<SyncService>;
|
||||
let accountService: FakeAccountService;
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||
|
||||
beforeEach(() => {
|
||||
organizationService = mock<OrganizationService>();
|
||||
organizationService.organizations$.mockReturnValue(of([]));
|
||||
syncServiceMock = mock<SyncService>();
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
@@ -25,15 +25,14 @@ describe("UnsecuredWebsitesReportComponent", () => {
|
||||
let organizationService: MockProxy<OrganizationService>;
|
||||
let syncServiceMock: MockProxy<SyncService>;
|
||||
let collectionService: MockProxy<CollectionService>;
|
||||
let accountService: FakeAccountService;
|
||||
const userId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||
|
||||
beforeEach(() => {
|
||||
organizationService = mock<OrganizationService>();
|
||||
organizationService.organizations$.mockReturnValue(of([]));
|
||||
syncServiceMock = mock<SyncService>();
|
||||
collectionService = mock<CollectionService>();
|
||||
accountService = mockAccountServiceWith(userId);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user